1 /**
2 	Build platform identification and specification matching.
3 
4 	This module is useful for determining the build platform for a certain
5 	machine and compiler invocation. Example applications include classifying
6 	CI slave machines.
7 
8 	It also contains means to match build platforms against a platform
9 	specification string as used in package reciptes.
10 
11 	Copyright: © 2012-2017 rejectedsoftware e.K.
12 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
13 	Authors: Sönke Ludwig
14 */
15 module dub.platform;
16 
17 import std.array;
18 
19 // archCheck, compilerCheck, and platformCheck are used below and in
20 // generatePlatformProbeFile, so they've been extracted into these strings
21 // that can be reused.
22 // Try to not use phobos in the probes to avoid long import times.
23 /// private
24 enum string platformCheck = q{
25 	string[] ret;
26 	version(Windows) ret ~= "windows";
27 	version(linux) ret ~= "linux";
28 	version(Posix) ret ~= "posix";
29 	version(OSX) ret ~= "osx";
30 	version(FreeBSD) ret ~= "freebsd";
31 	version(OpenBSD) ret ~= "openbsd";
32 	version(NetBSD) ret ~= "netbsd";
33 	version(DragonFlyBSD) ret ~= "dragonflybsd";
34 	version(BSD) ret ~= "bsd";
35 	version(Solaris) ret ~= "solaris";
36 	version(AIX) ret ~= "aix";
37 	version(Haiku) ret ~= "haiku";
38 	version(SkyOS) ret ~= "skyos";
39 	version(SysV3) ret ~= "sysv3";
40 	version(SysV4) ret ~= "sysv4";
41 	version(Hurd) ret ~= "hurd";
42 	version(Android) ret ~= "android";
43 	version(Cygwin) ret ~= "cygwin";
44 	version(MinGW) ret ~= "mingw";
45 	version(WebAssembly) ret ~= "wasm";
46 	return ret;
47 };
48 
49 /// private
50 enum string archCheck = q{
51 	string[] ret;
52 	version(X86) ret ~= "x86";
53 	version(X86_64) ret ~= "x86_64";
54 	version(ARM) ret ~= "arm";
55 	version(AArch64) ret ~= "aarch64";
56 	version(ARM_Thumb) ret ~= "arm_thumb";
57 	version(ARM_SoftFloat) ret ~= "arm_softfloat";
58 	version(ARM_HardFloat) ret ~= "arm_hardfloat";
59 	version(PPC) ret ~= "ppc";
60 	version(PPC_SoftFP) ret ~= "ppc_softfp";
61 	version(PPC_HardFP) ret ~= "ppc_hardfp";
62 	version(PPC64) ret ~= "ppc64";
63 	version(IA64) ret ~= "ia64";
64 	version(MIPS) ret ~= "mips";
65 	version(MIPS32) ret ~= "mips32";
66 	version(MIPS64) ret ~= "mips64";
67 	version(MIPS_O32) ret ~= "mips_o32";
68 	version(MIPS_N32) ret ~= "mips_n32";
69 	version(MIPS_O64) ret ~= "mips_o64";
70 	version(MIPS_N64) ret ~= "mips_n64";
71 	version(MIPS_EABI) ret ~= "mips_eabi";
72 	version(MIPS_NoFloat) ret ~= "mips_nofloat";
73 	version(MIPS_SoftFloat) ret ~= "mips_softfloat";
74 	version(MIPS_HardFloat) ret ~= "mips_hardfloat";
75 	version(SPARC) ret ~= "sparc";
76 	version(SPARC_V8Plus) ret ~= "sparc_v8plus";
77 	version(SPARC_SoftFP) ret ~= "sparc_softfp";
78 	version(SPARC_HardFP) ret ~= "sparc_hardfp";
79 	version(SPARC64) ret ~= "sparc64";
80 	version(S390) ret ~= "s390";
81 	version(S390X) ret ~= "s390x";
82 	version(HPPA) ret ~= "hppa";
83 	version(HPPA64) ret ~= "hppa64";
84 	version(SH) ret ~= "sh";
85 	version(SH64) ret ~= "sh64";
86 	version(Alpha) ret ~= "alpha";
87 	version(Alpha_SoftFP) ret ~= "alpha_softfp";
88 	version(Alpha_HardFP) ret ~= "alpha_hardfp";
89 	return ret;
90 };
91 
92 /// private
93 enum string compilerCheck = q{
94 	version(DigitalMars) return "dmd";
95 	else version(GNU) return "gdc";
96 	else version(LDC) return "ldc";
97 	else version(SDC) return "sdc";
98 	else return null;
99 };
100 
101 /** Determines the full build platform used for the current build.
102 
103 	Note that the `BuildPlatform.compilerBinary` field will be left empty.
104 
105 	See_Also: `determinePlatform`, `determineArchitecture`, `determineCompiler`
106 */
107 BuildPlatform determineBuildPlatform()
108 {
109 	BuildPlatform ret;
110 	ret.platform = determinePlatform();
111 	ret.architecture = determineArchitecture();
112 	ret.compiler = determineCompiler();
113 	ret.frontendVersion = __VERSION__;
114 	return ret;
115 }
116 
117 
118 /** Returns a list of platform identifiers that apply to the current
119 	build.
120 
121 	Example results are `["windows"]` or `["posix", "osx"]`. The identifiers
122 	correspond to the compiler defined version constants built into the
123 	language, except that they are converted to lower case.
124 
125 	See_Also: `determineBuildPlatform`
126 */
127 string[] determinePlatform()
128 {
129 	mixin(platformCheck);
130 }
131 
132 /** Returns a list of architecture identifiers that apply to the current
133 	build.
134 
135 	Example results are `["x86_64"]` or `["arm", "arm_softfloat"]`. The
136 	identifiers correspond to the compiler defined version constants built into
137 	the language, except that they are converted to lower case.
138 
139 	See_Also: `determineBuildPlatform`
140 */
141 string[] determineArchitecture()
142 {
143 	mixin(archCheck);
144 }
145 
146 /** Determines the canonical compiler name used for the current build.
147 
148 	The possible values currently are "dmd", "gdc", "ldc" or "sdc". If an
149 	unknown compiler is used, this function will return an empty string.
150 
151 	See_Also: `determineBuildPlatform`
152 */
153 string determineCompiler()
154 {
155 	mixin(compilerCheck);
156 }
157 
158 /** Matches a platform specification string against a build platform.
159 
160 	Specifications are build upon the following scheme, where each component
161 	is optional (indicated by []), but the order is obligatory:
162 	"[-platform][-architecture][-compiler]"
163 
164 	So the following strings are valid specifications: `"-windows-x86-dmd"`,
165 	`"-dmd"`, `"-arm"`, `"-arm-dmd"`, `"-windows-dmd"`
166 
167 	Params:
168 		platform = The build platform to match agains the platform specification
169 	    specification = The specification being matched. It must either be an
170 	    	empty string or start with a dash.
171 
172 	Returns:
173 	    `true` if the given specification matches the build platform, `false`
174 	    otherwise. Using an empty string as the platform specification will
175 	    always result in a match.
176 */
177 bool matchesSpecification(in BuildPlatform platform, const(char)[] specification)
178 {
179 	import std.string : format;
180 	import std.algorithm : canFind, splitter;
181 	import std.exception : enforce;
182 
183 	if (specification.empty) return true;
184 	if (platform == BuildPlatform.any) return true;
185 
186 	auto splitted = specification.splitter('-');
187 	assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!");
188 	splitted.popFront(); // Drop leading empty match.
189 	enforce(!splitted.empty, format("Platform specification, if present, must not be empty: \"%s\"", specification));
190 
191 	if (platform.platform.canFind(splitted.front)) {
192 		splitted.popFront();
193 		if (splitted.empty)
194 			return true;
195 	}
196 	if (platform.architecture.canFind(splitted.front)) {
197 		splitted.popFront();
198 		if (splitted.empty)
199 			return true;
200 	}
201 	if (platform.compiler == splitted.front) {
202 		splitted.popFront();
203 		enforce(splitted.empty, "No valid specification! The compiler has to be the last element: " ~ specification);
204 		return true;
205 	}
206 	return false;
207 }
208 
209 ///
210 unittest {
211 	auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd");
212 	assert(platform.matchesSpecification(""));
213 	assert(platform.matchesSpecification("-posix"));
214 	assert(platform.matchesSpecification("-linux"));
215 	assert(platform.matchesSpecification("-linux-dmd"));
216 	assert(platform.matchesSpecification("-linux-x86_64-dmd"));
217 	assert(platform.matchesSpecification("-x86_64"));
218 	assert(!platform.matchesSpecification("-windows"));
219 	assert(!platform.matchesSpecification("-ldc"));
220 	assert(!platform.matchesSpecification("-windows-dmd"));
221 }
222 
223 /// Represents a platform a package can be build upon.
224 struct BuildPlatform {
225 	/// Special constant used to denote matching any build platform.
226 	enum any = BuildPlatform(null, null, null, null, -1);
227 
228 	/// Platform identifiers, e.g. ["posix", "windows"]
229 	string[] platform;
230 	/// CPU architecture identifiers, e.g. ["x86", "x86_64"]
231 	string[] architecture;
232 	/// Canonical compiler name e.g. "dmd"
233 	string compiler;
234 	/// Compiler binary name e.g. "ldmd2"
235 	string compilerBinary;
236 	/// Compiled frontend version (e.g. `2067` for frontend versions 2.067.x)
237 	int frontendVersion;
238 	/// Compiler version e.g. "1.11.0"
239 	string compilerVersion;
240 	/// Frontend version string from frontendVersion
241 	/// e.g: 2067 => "2.067"
242 	string frontendVersionString() const
243 	{
244 		import std.format : format;
245 
246 		const maj = frontendVersion / 1000;
247 		const min = frontendVersion % 1000;
248 		return format("%d.%03d", maj, min);
249 	}
250 	///
251 	unittest
252 	{
253 		BuildPlatform bp;
254 		bp.frontendVersion = 2067;
255 		assert(bp.frontendVersionString == "2.067");
256 	}
257 }
258 
259