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 		if (tt != TargetType.none && tt != TargetType.sourceLibrary && shallowbs.sourceFiles.empty) {
84 			logWarn(`Package %s contains no source files. Please add {"targetType": "none"} to it's package description to avoid building it.`,
85 				pack.name);
86 			tt = TargetType.none;
87 		}
88 
89 
90 		shallowbs.targetType = tt;
91 		bool generates_binary = tt != TargetType.sourceLibrary && tt != TargetType.none;
92 
93 		enforce (generates_binary || pack !is m_project.mainPackage,
94 			format("Main package must have a binary target type, not %s. Cannot build.", tt));
95 
96 		if (tt == TargetType.none) {
97 			// ignore any build settings for targetType none (only dependencies will be processed)
98 			shallowbs = BuildSettings.init;
99 		}
100 
101 		// start to build up the build settings
102 		BuildSettings buildsettings = settings.buildSettings.dup;
103 		processVars(buildsettings, pack.path.toNativeString(), shallowbs, true);
104 		buildsettings.addVersions("Have_" ~ stripDlangSpecialChars(pack.name));
105 
106 		// remove any mainSourceFile from library builds
107 		if (buildsettings.targetType != TargetType.executable && buildsettings.mainSourceFile.length) {
108 			buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => f != buildsettings.mainSourceFile)().array;
109 			main_files ~= buildsettings.mainSourceFile;
110 		}
111 
112 		logDiagnostic("Generate target %s (%s %s %s)", pack.name, buildsettings.targetType, buildsettings.targetPath, buildsettings.targetName);
113 		if (generates_binary)
114 			targets[pack.name] = TargetInfo(pack, [pack], configs[pack.name], buildsettings, null);
115 
116 		foreach (depname, depspec; pack.dependencies) {
117 			if (!pack.hasDependency(depname, configs[pack.name])) continue;
118 			auto dep = m_project.getDependency(depname, depspec.optional);
119 			if (!dep) continue;
120 
121 			auto depbs = collect(settings, dep, targets, configs, main_files, generates_binary ? pack.name : bin_pack);
122 
123 			if (depbs.targetType != TargetType.sourceLibrary && depbs.targetType != TargetType.none) {
124 				// add a reference to the target binary and remove all source files in the dependency build settings
125 				depbs.sourceFiles = depbs.sourceFiles.filter!(f => f.isLinkerFile()).array;
126 				depbs.importFiles = null;
127 			}
128 
129 			buildsettings.add(depbs);
130 
131 			auto pt = (generates_binary ? pack.name : bin_pack) in targets;
132 			assert(pt !is null);
133 			if (auto pdt = depname in targets) {
134 				pt.dependencies ~= depname;
135 				pt.linkDependencies ~= depname;
136 				if (depbs.targetType == TargetType.staticLibrary)
137 					pt.linkDependencies = pt.linkDependencies.filter!(d => !pdt.linkDependencies.canFind(d)).array ~ pdt.linkDependencies;
138 			} else pt.packages ~= dep;
139 		}
140 
141 		if (generates_binary) {
142 			// add build type settings and convert plain DFLAGS to build options
143 			m_project.addBuildTypeSettings(buildsettings, settings.platform, settings.buildType);
144 			settings.compiler.extractBuildOptions(buildsettings);
145 			enforceBuildRequirements(buildsettings);
146 			targets[pack.name].buildSettings = buildsettings.dup;
147 		}
148 
149 		return buildsettings;
150 	}
151 
152 	private void downwardsInheritSettings(string target, TargetInfo[string] targets, in BuildSettings root_settings)
153 	{
154 		auto ti = &targets[target];
155 		ti.buildSettings.addVersions(root_settings.versions);
156 		ti.buildSettings.addDebugVersions(root_settings.debugVersions);
157 		ti.buildSettings.addOptions(root_settings.options);
158 		ti.buildSettings.prependStringImportPaths(root_settings.stringImportPaths);
159 
160 		foreach (d; ti.dependencies)
161 			downwardsInheritSettings(d, targets, root_settings);
162 	}
163 }
164 
165 
166 struct GeneratorSettings {
167 	BuildPlatform platform;
168 	Compiler compiler;
169 	string config;
170 	string buildType;
171 	BuildSettings buildSettings;
172 
173 	bool combined; // compile all in one go instead of each dependency separately
174 
175 	// only used for generator "build"
176 	bool run, force, direct, clean, rdmd;
177 	string[] runArgs;
178 }
179 
180 
181 /**
182 	Creates a project generator of the given type for the specified project.
183 */
184 ProjectGenerator createProjectGenerator(string generator_type, Project app, PackageManager mgr)
185 {
186 	assert(app !is null && mgr !is null, "Project and package manager needed to create a generator.");
187 
188 	generator_type = generator_type.toLower();
189 	switch(generator_type) {
190 		default:
191 			throw new Exception("Unknown project generator: "~generator_type);
192 		case "build":
193 			logDebug("Creating build generator.");
194 			return new BuildGenerator(app, mgr);
195 		case "mono-d":
196 			throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead.");
197 		case "visuald":
198 			logDebug("Creating VisualD generator.");
199 			return new VisualDGenerator(app, mgr);
200 	}
201 }
202 
203 
204 /**
205 	Runs pre-build commands and performs other required setup before project files are generated.
206 */
207 void prepareGeneration(in BuildSettings buildsettings)
208 {
209 	if( buildsettings.preGenerateCommands.length ){
210 		logInfo("Running pre-generate commands...");
211 		runBuildCommands(buildsettings.preGenerateCommands, buildsettings);
212 	}
213 }
214 
215 /**
216 	Runs post-build commands and copies required files to the binary directory.
217 */
218 void finalizeGeneration(in BuildSettings buildsettings, bool generate_binary)
219 {
220 	if (buildsettings.postGenerateCommands.length) {
221 		logInfo("Running post-generate commands...");
222 		runBuildCommands(buildsettings.postGenerateCommands, buildsettings);
223 	}
224 
225 	if (generate_binary) {
226 		if (!exists(buildsettings.targetPath))
227 			mkdirRecurse(buildsettings.targetPath);
228 		
229 		if (buildsettings.copyFiles.length) {
230 			logInfo("Copying files...");
231 			foreach (f; buildsettings.copyFiles) {
232 				auto src = Path(f);
233 				auto dst = Path(buildsettings.targetPath) ~ Path(f).head;
234 				logDiagnostic("  %s to %s", src.toNativeString(), dst.toNativeString());
235 				try {
236 					copyFile(src, dst, true);
237 				} catch logWarn("Failed to copy to %s", dst.toNativeString());
238 			}
239 		}
240 	}
241 }
242 
243 void runBuildCommands(in string[] commands, in BuildSettings build_settings)
244 {
245 	import std.process;
246 	import dub.internal.utils;
247 
248 	string[string] env = environment.toAA();
249 	// TODO: do more elaborate things here
250 	// TODO: escape/quote individual items appropriately
251 	env["DFLAGS"] = join(cast(string[])build_settings.dflags, " ");
252 	env["LFLAGS"] = join(cast(string[])build_settings.lflags," ");
253 	env["VERSIONS"] = join(cast(string[])build_settings.versions," ");
254 	env["LIBS"] = join(cast(string[])build_settings.libs," ");
255 	env["IMPORT_PATHS"] = join(cast(string[])build_settings.importPaths," ");
256 	env["STRING_IMPORT_PATHS"] = join(cast(string[])build_settings.stringImportPaths," ");
257 	runCommands(commands, env);
258 }