1 /**
2 	Compiler settings and abstraction.
3 
4 	Copyright: © 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.compilers.compiler;
9 
10 import dub.compilers.dmd;
11 import dub.compilers.gdc;
12 import dub.compilers.ldc;
13 import dub.internal.std.process;
14 import dub.internal.vibecompat.core.log;
15 import dub.internal.vibecompat.data.json;
16 import dub.internal.vibecompat.inet.path;
17 
18 import std.algorithm;
19 import std.array;
20 import std.conv;
21 import std.exception;
22 
23 
24 static this()
25 {
26 	registerCompiler(new DmdCompiler);
27 	registerCompiler(new GdcCompiler);
28 	registerCompiler(new LdcCompiler);
29 }
30 
31 
32 Compiler getCompiler(string name)
33 {
34 	foreach (c; s_compilers)
35 		if (c.name == name)
36 			return c;
37 
38 	// try to match names like gdmd or gdc-2.61
39 	if (name.canFind("dmd")) return getCompiler("dmd");
40 	if (name.canFind("gdc")) return getCompiler("gdc");
41 	if (name.canFind("ldc")) return getCompiler("ldc");
42 			
43 	throw new Exception("Unknown compiler: "~name);
44 }
45 
46 void registerCompiler(Compiler c)
47 {
48 	s_compilers ~= c;
49 }
50 
51 void warnOnSpecialCompilerFlags(string[] compiler_flags, string package_name, string config_name)
52 {
53 	struct SpecialFlag {
54 		string[] flags;
55 		string alternative;
56 	}
57 	static immutable SpecialFlag[] s_specialFlags = [
58 		{["-c", "-o-"], "Automatically issued by DUB, do not specify in package.json"},
59 		{[ "-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`},
60 		{["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"},
61 		{["-wi"], `Use the "buildRequirements" field to control warning behavior`},
62 		{["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`},
63 		{["-of"], `Use "targetPath" and "targetName" to customize the output file`},
64 		{["-debug", "-fdebug", "-g"], "Call dub with --build=debug"},
65 		{["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"},
66 		{["-unittest", "-funittest"], "Call dub with --build=unittest"},
67 		{["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`},
68 		{["-D"], "Call dub with --build=docs or --build=ddox"},
69 		{["-X"], "Call dub with --build=ddox"},
70 		{["-cov"], "Call dub with --build=cov or --build=unittest-cox"},
71 		{["-profile"], "Call dub with --build=profile"},
72 		{["-version="], `Use "versions" to specify version constants in a compiler independent way`},
73 		{["-debug=", `Use "debugVersions" to specify version constants in a compiler independent way`]},
74 		{["-I"], `Use "importPaths" to specify import paths in a compiler independent way`},
75 		{["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`},
76 	];
77 
78 	bool got_preamble = false;
79 	void outputPreamble()
80 	{
81 		if (got_preamble) return;
82 		got_preamble = true;
83 		logWarn("");
84 		if (config_name.empty) logWarn("## Warning for package %s ##", package_name);
85 		else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name);
86 		logWarn("");
87 		logWarn("The following compiler flags have been specified in the package description");
88 		logWarn("file. They are handled by DUB and direct use in packages is discouraged.");
89 		logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags");
90 		logWarn("to the compiler, or use one of the suggestions below:");
91 		logWarn("");
92 	}
93 
94 	foreach (f; compiler_flags) {
95 		foreach (sf; s_specialFlags) {
96 			if (sf.flags.canFind!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) {
97 				outputPreamble();
98 				logWarn("%s: %s", f, sf.alternative);
99 				break;
100 			}
101 		}
102 	}
103 
104 	if (got_preamble) logWarn("");
105 }
106 
107 
108 /**
109 	Replaces each referenced import library by the appropriate linker flags.
110 
111 	This function tries to invoke "pkg-config" if possible and falls back to
112 	direct flag translation if that fails.
113 */
114 void resolveLibs(ref BuildSettings settings)
115 {
116 	if (settings.libs.length == 0) return;
117 
118 	if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) {
119 		logDiagnostic("Ignoring all import libraries for static library build.");
120 		settings.libs = null;
121 		version(Windows) settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array;
122 	}
123 	
124 	try {
125 		logDiagnostic("Trying to use pkg-config to resolve library flags for %s.", settings.libs);
126 		auto libflags = execute(["pkg-config", "--libs"] ~ settings.libs.map!(l => "lib"~l)().array());
127 		enforce(libflags.status == 0, "pkg-config exited with error code "~to!string(libflags.status));
128 		foreach (f; libflags.output.split()) {
129 			if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(","));
130 			else settings.addLFlags(f);
131 		}
132 	} catch (Exception e) {
133 		logDiagnostic("pkg-config failed: %s", e.msg);
134 		logDiagnostic("Falling back to direct -lxyz flags.");
135 		version(Windows) settings.addSourceFiles(settings.libs.map!(l => l~".lib")().array());
136 		else settings.addLFlags(settings.libs.map!(l => "-l"~l)().array());
137 	}
138 	settings.libs = null;
139 }
140 
141 
142 interface Compiler {
143 	@property string name() const;
144 
145 	BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null);
146 
147 	/// Replaces high level fields with low level fields and converts
148 	/// dmd flags to compiler-specific flags
149 	void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all);
150 
151 	/// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field.
152 	void extractBuildOptions(ref BuildSettings settings);
153 
154 	/// Adds the appropriate flag to set a target path
155 	void setTarget(ref BuildSettings settings, in BuildPlatform platform);
156 
157 	/// Invokes the compiler using the given flags
158 	void invoke(in BuildSettings settings, in BuildPlatform platform);
159 
160 	/// Invokes the underlying linker directly
161 	void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects);
162 
163 	final protected void enforceBuildRequirements(ref BuildSettings settings)
164 	{
165 		settings.addOptions(BuildOptions.warnings);
166 		if (settings.requirements & BuildRequirements.allowWarnings) settings.options &= ~BuildOptions.warningsAsErrors;
167 		if (settings.requirements & BuildRequirements.silenceWarnings) settings.options &= ~(BuildOptions.warningsAsErrors|BuildOptions.warnings);
168 		if (settings.requirements & BuildRequirements.disallowDeprecations) { settings.options &= ~(BuildOptions.ignoreDeprecations|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.deprecationErrors; }
169 		if (settings.requirements & BuildRequirements.silenceDeprecations) { settings.options &= ~(BuildOptions.deprecationErrors|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.ignoreDeprecations; }
170 		if (settings.requirements & BuildRequirements.disallowInlining) settings.options &= BuildOptions.inline;
171 		if (settings.requirements & BuildRequirements.disallowOptimization) settings.options &= ~BuildOptions.optimize;
172 		if (settings.requirements & BuildRequirements.requireBoundsCheck) settings.options &= ~BuildOptions.noBoundsChecks;
173 		if (settings.requirements & BuildRequirements.requireContracts) settings.options &= ~BuildOptions.release;
174 		if (settings.requirements & BuildRequirements.relaxProperties) settings.options &= ~BuildOptions.property;
175 	}
176 }
177 
178 
179 /// BuildPlatform specific settings, like needed libraries or additional
180 /// include paths.
181 struct BuildSettings {
182 	TargetType targetType;
183 	string targetPath;
184 	string targetName;
185 	string workingDirectory;
186 	string[] dflags;
187 	string[] lflags;
188 	string[] libs;
189 	string[] sourceFiles;
190 	string[] copyFiles;
191 	string[] versions;
192 	string[] debugVersions;
193 	string[] importPaths;
194 	string[] stringImportPaths;
195 	string[] preGenerateCommands;
196 	string[] postGenerateCommands;
197 	string[] preBuildCommands;
198 	string[] postBuildCommands;
199 	BuildRequirements requirements;
200 	BuildOptions options;
201 
202 	void addDFlags(in string[] value...) { dflags ~= value; }
203 	void removeDFlags(in string[] value...) { remove(dflags, value); }
204 	void addLFlags(in string[] value...) { lflags ~= value; }
205 	void addLibs(in string[] value...) { add(libs, value); }
206 	void addSourceFiles(in string[] value...) { add(sourceFiles, value); }
207 	void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); }
208 	void addCopyFiles(in string[] value...) { add(copyFiles, value); }
209 	void addVersions(in string[] value...) { add(versions, value); }
210 	void addDebugVersions(in string[] value...) { add(debugVersions, value); }
211 	void addImportPaths(in string[] value...) { add(importPaths, value); }
212 	void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); }
213 	void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); }
214 	void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); }
215 	void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); }
216 	void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); }
217 	void addRequirements(in BuildRequirements[] value...) { foreach (v; value) this.requirements |= v; }
218 	void addOptions(in BuildOptions[] value...) { foreach (v; value) this.options |= v; }
219 	void removeOptions(in BuildOptions[] value...) { foreach (v; value) this.options &= ~v; }
220 
221 	// Adds vals to arr without adding duplicates.
222 	private void add(ref string[] arr, in string[] vals, bool no_duplicates = true)
223 	{
224 		if (!no_duplicates) {
225 			arr ~= vals;
226 			return;
227 		}
228 
229 		foreach (v; vals) {
230 			bool found = false;
231 			foreach (i; 0 .. arr.length)
232 				if (arr[i] == v) {
233 					found = true;
234 					break;
235 				}
236 			if (!found) arr ~= v;
237 		}
238 	}
239 
240 	private void removePaths(ref string[] arr, in string[] vals)
241 	{
242 		bool matches(string s)
243 		{
244 			foreach (p; vals)
245 				if (Path(s) == Path(p))
246 					return true;
247 			return false;
248 		}
249 		arr = arr.filter!(s => !matches(s))().array();
250 	}
251 
252 	private void remove(ref string[] arr, in string[] vals)
253 	{
254 		bool matches(string s)
255 		{
256 			foreach (p; vals)
257 				if (s == p)
258 					return true;
259 			return false;
260 		}
261 		arr = arr.filter!(s => !matches(s))().array();
262 	}
263 }
264 
265 /// Represents a platform a package can be build upon.
266 struct BuildPlatform {
267 	/// e.g. ["posix", "windows"]
268 	string[] platform;
269 	/// e.g. ["x86", "x86_64"]
270 	string[] architecture;
271 	/// Canonical compiler name e.g. "dmd"
272 	string compiler;
273 	/// Compiler binary name e.g. "ldmd2"
274 	string compilerBinary;
275 
276 	/// Build platforms can be specified via a string specification.
277 	///
278 	/// Specifications are build upon the following scheme, where each component
279 	/// is optional (indicated by []), but the order is obligatory.
280 	/// "[-platform][-architecture][-compiler]"
281 	///
282 	/// So the following strings are valid specifications:
283 	/// "-windows-x86-dmd"
284 	/// "-dmd"
285 	/// "-arm"
286 	/// "-arm-dmd"
287 	/// "-windows-dmd"
288 	///
289 	/// Params:
290 	///     specification = The specification being matched. It must be the empty string or start with a dash.  
291 	///
292 	/// Returns: 
293 	///     true if the given specification matches this BuildPlatform, false otherwise. (The empty string matches)
294 	///
295 	bool matchesSpecification(const(char)[] specification) const {
296 		if (specification.empty)
297 			return true;
298 		auto splitted=specification.splitter('-');
299 		assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!");
300 		splitted.popFront(); // Drop leading empty match.
301 		enforce(!splitted.empty, "Platform specification if present, must not be empty!");
302 		if (platform.canFind(splitted.front)) {
303 			splitted.popFront();
304 			if(splitted.empty)
305 			    return true;
306 		}
307 		if (architecture.canFind(splitted.front)) {
308 			splitted.popFront();
309 			if(splitted.empty)
310 			    return true;
311 		}
312 		if (compiler == splitted.front) {
313 			splitted.popFront();
314 			enforce(splitted.empty, "No valid specification! The compiler has to be the last element!");
315 			return true;
316 		}
317 		return false;
318 	}
319 	unittest {
320 		auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd");
321 		assert(platform.matchesSpecification("-posix"));
322 		assert(platform.matchesSpecification("-linux"));
323 		assert(platform.matchesSpecification("-linux-dmd"));
324 		assert(platform.matchesSpecification("-linux-x86_64-dmd"));
325 		assert(platform.matchesSpecification("-x86_64"));
326 		assert(!platform.matchesSpecification("-windows"));
327 		assert(!platform.matchesSpecification("-ldc"));
328 		assert(!platform.matchesSpecification("-windows-dmd"));
329 	}
330 }
331 
332 enum BuildSetting {
333 	dflags            = 1<<0,
334 	lflags            = 1<<1,
335 	libs              = 1<<2,
336 	sourceFiles       = 1<<3,
337 	copyFiles         = 1<<4,
338 	versions          = 1<<5,
339 	debugVersions     = 1<<6,
340 	importPaths       = 1<<7,
341 	stringImportPaths = 1<<8,
342 	options           = 1<<9,
343 	none = 0,
344 	commandLine = dflags|copyFiles,
345 	commandLineSeparate = commandLine|lflags,
346 	all = dflags|lflags|libs|sourceFiles|copyFiles|versions|debugVersions|importPaths|stringImportPaths|options,
347 	noOptions = all & ~options
348 }
349 
350 enum TargetType {
351 	autodetect,
352 	none,
353 	executable,
354 	library,
355 	sourceLibrary,
356 	dynamicLibrary,
357 	staticLibrary
358 }
359 
360 enum BuildRequirements {
361 	none = 0,                     /// No special requirements
362 	allowWarnings        = 1<<0,  /// Warnings do not abort compilation
363 	silenceWarnings      = 1<<1,  /// Don't show warnings
364 	disallowDeprecations = 1<<2,  /// Using deprecated features aborts compilation
365 	silenceDeprecations  = 1<<3,  /// Don't show deprecation warnings
366 	disallowInlining     = 1<<4,  /// Avoid function inlining, even in release builds
367 	disallowOptimization = 1<<5,  /// Avoid optimizations, even in release builds
368 	requireBoundsCheck   = 1<<6,  /// Always perform bounds checks
369 	requireContracts     = 1<<7,  /// Leave assertions and contracts enabled in release builds
370 	relaxProperties      = 1<<8,  /// DEPRECATED: Do not enforce strict property handling (-property)
371 	noDefaultFlags       = 1<<9,  /// Do not issue any of the default build flags (e.g. -debug, -w, -property etc.) - use only for development purposes
372 }
373 
374 enum BuildOptions {
375 	none = 0,                     /// Use compiler defaults
376 	debug_ = 1<<0,                /// Compile in debug mode (enables contracts, -debug)
377 	release = 1<<1,               /// Compile in release mode (disables assertions and bounds checks, -release)
378 	coverage = 1<<2,              /// Enable code coverage analysis (-cov)
379 	debugInfo = 1<<3,             /// Enable symbolic debug information (-g)
380 	debugInfoC = 1<<4,            /// Enable symbolic debug information in C compatible form (-gc)
381 	alwaysStackFrame = 1<<5,      /// Always generate a stack frame (-gs)
382 	stackStomping = 1<<6,         /// Perform stack stomping (-gx)
383 	inline = 1<<7,                /// Perform function inlining (-inline)
384 	noBoundsChecks = 1<<8,        /// Disable all bounds checking (-noboundscheck)
385 	optimize = 1<<9,              /// Enable optimizations (-O)
386 	profile = 1<<10,              /// Emit profiling code (-profile)
387 	unittests = 1<<11,            /// Compile unit tests (-unittest)
388 	verbose = 1<<12,              /// Verbose compiler output (-v)
389 	ignoreUnknownPragmas = 1<<13, /// Ignores unknown pragmas during compilation (-ignore)
390 	syntaxOnly = 1<<14,           /// Don't generate object files (-o-)
391 	warnings = 1<<15,             /// Enable warnings (-wi)
392 	warningsAsErrors = 1<<16,     /// Treat warnings as errors (-w)
393 	ignoreDeprecations = 1<<17,   /// Do not warn about using deprecated features (-d)
394 	deprecationWarnings = 1<<18,  /// Warn about using deprecated features (-dw)
395 	deprecationErrors = 1<<19,    /// Stop compilation upon usage of deprecated features (-de)
396 	property = 1<<20,             /// DEPRECATED: Enforce property syntax (-property)
397 }
398 
399 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform)
400 {
401 	assert(settings.targetName.length > 0, "No target name set.");
402 	final switch (settings.targetType) {
403 		case TargetType.autodetect: assert(false, "Configurations must have a concrete target type.");
404 		case TargetType.none: return null;
405 		case TargetType.sourceLibrary: return null;
406 		case TargetType.executable:
407 			if( platform.platform.canFind("windows") )
408 				return settings.targetName ~ ".exe";
409 			else return settings.targetName;
410 		case TargetType.library:
411 		case TargetType.staticLibrary:
412 			if( platform.platform.canFind("windows") )
413 				return settings.targetName ~ ".lib";
414 			else return "lib" ~ settings.targetName ~ ".a";
415 		case TargetType.dynamicLibrary:
416 			if( platform.platform.canFind("windows") )
417 				return settings.targetName ~ ".dll";
418 			else return "lib" ~ settings.targetName ~ ".so";
419 	}
420 } 
421 
422 
423 
424 private {
425 	Compiler[] s_compilers;
426 }