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 	return ret;
46 };
47 
48 /// private
49 enum string archCheck = q{
50 	string[] ret;
51 	version(X86) ret ~= "x86";
52 	version(X86_64) ret ~= "x86_64";
53 	version(ARM) ret ~= "arm";
54 	version(ARM_Thumb) ret ~= "arm_thumb";
55 	version(ARM_SoftFloat) ret ~= "arm_softfloat";
56 	version(ARM_HardFloat) ret ~= "arm_hardfloat";
57 	version(ARM64) ret ~= "arm64";
58 	version(PPC) ret ~= "ppc";
59 	version(PPC_SoftFP) ret ~= "ppc_softfp";
60 	version(PPC_HardFP) ret ~= "ppc_hardfp";
61 	version(PPC64) ret ~= "ppc64";
62 	version(IA64) ret ~= "ia64";
63 	version(MIPS) ret ~= "mips";
64 	version(MIPS32) ret ~= "mips32";
65 	version(MIPS64) ret ~= "mips64";
66 	version(MIPS_O32) ret ~= "mips_o32";
67 	version(MIPS_N32) ret ~= "mips_n32";
68 	version(MIPS_O64) ret ~= "mips_o64";
69 	version(MIPS_N64) ret ~= "mips_n64";
70 	version(MIPS_EABI) ret ~= "mips_eabi";
71 	version(MIPS_NoFloat) ret ~= "mips_nofloat";
72 	version(MIPS_SoftFloat) ret ~= "mips_softfloat";
73 	version(MIPS_HardFloat) ret ~= "mips_hardfloat";
74 	version(SPARC) ret ~= "sparc";
75 	version(SPARC_V8Plus) ret ~= "sparc_v8plus";
76 	version(SPARC_SoftFP) ret ~= "sparc_softfp";
77 	version(SPARC_HardFP) ret ~= "sparc_hardfp";
78 	version(SPARC64) ret ~= "sparc64";
79 	version(S390) ret ~= "s390";
80 	version(S390X) ret ~= "s390x";
81 	version(HPPA) ret ~= "hppa";
82 	version(HPPA64) ret ~= "hppa64";
83 	version(SH) ret ~= "sh";
84 	version(SH64) ret ~= "sh64";
85 	version(Alpha) ret ~= "alpha";
86 	version(Alpha_SoftFP) ret ~= "alpha_softfp";
87 	version(Alpha_HardFP) ret ~= "alpha_hardfp";
88 	return ret;
89 };
90 
91 /// private
92 enum string compilerCheck = q{
93 	version(DigitalMars) return "dmd";
94 	else version(GNU) return "gdc";
95 	else version(LDC) return "ldc";
96 	else version(SDC) return "sdc";
97 	else return null;
98 };
99 
100 /** Determines the full build platform used for the current build.
101 
102 	Note that the `BuildPlatform.compilerBinary` field will be left empty.
103 
104 	See_Also: `determinePlatform`, `determineArchitecture`, `determineCompiler`
105 */
106 BuildPlatform determineBuildPlatform()
107 {
108 	BuildPlatform ret;
109 	ret.platform = determinePlatform();
110 	ret.architecture = determineArchitecture();
111 	ret.compiler = determineCompiler();
112 	ret.frontendVersion = __VERSION__;
113 	return ret;
114 }
115 
116 
117 /** Returns a list of platform identifiers that apply to the current
118 	build.
119 
120 	Example results are `["windows"]` or `["posix", "osx"]`. The identifiers
121 	correspond to the compiler defined version constants built into the
122 	language, except that they are converted to lower case.
123 
124 	See_Also: `determineBuildPlatform`
125 */
126 string[] determinePlatform()
127 {
128 	mixin(platformCheck);
129 }
130 
131 /** Returns a list of architecture identifiers that apply to the current
132 	build.
133 
134 	Example results are `["x86_64"]` or `["arm", "arm_softfloat"]`. The
135 	identifiers correspond to the compiler defined version constants built into
136 	the language, except that they are converted to lower case.
137 
138 	See_Also: `determineBuildPlatform`
139 */
140 string[] determineArchitecture()
141 {
142 	mixin(archCheck);
143 }
144 
145 /** Determines the canonical compiler name used for the current build.
146 
147 	The possible values currently are "dmd", "gdc", "ldc" or "sdc". If an
148 	unknown compiler is used, this function will return an empty string.
149 
150 	See_Also: `determineBuildPlatform`
151 */
152 string determineCompiler()
153 {
154 	mixin(compilerCheck);
155 }
156 
157 /** Matches a platform specification string against a build platform.
158 
159 	Specifications are build upon the following scheme, where each component
160 	is optional (indicated by []), but the order is obligatory:
161 	"[-platform][-architecture][-compiler]"
162 
163 	So the following strings are valid specifications: `"-windows-x86-dmd"`,
164 	`"-dmd"`, `"-arm"`, `"-arm-dmd"`, `"-windows-dmd"`
165 
166 	Params:
167 		platform = The build platform to match agains the platform specification
168 	    specification = The specification being matched. It must either be an
169 	    	empty string or start with a dash.
170 
171 	Returns:
172 	    `true` if the given specification matches the build platform, `false`
173 	    otherwise. Using an empty string as the platform specification will
174 	    always result in a match.
175 */
176 bool matchesSpecification(in BuildPlatform platform, const(char)[] specification)
177 {
178 	import std.string : format;
179 	import std.algorithm : canFind, splitter;
180 	import std.exception : enforce;
181 
182 	if (specification.empty) return true;
183 	if (platform == BuildPlatform.any) return true;
184 
185 	auto splitted = specification.splitter('-');
186 	assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!");
187 	splitted.popFront(); // Drop leading empty match.
188 	enforce(!splitted.empty, format("Platform specification, if present, must not be empty: \"%s\"", specification));
189 
190 	if (platform.platform.canFind(splitted.front)) {
191 		splitted.popFront();
192 		if (splitted.empty)
193 			return true;
194 	}
195 	if (platform.architecture.canFind(splitted.front)) {
196 		splitted.popFront();
197 		if (splitted.empty)
198 			return true;
199 	}
200 	if (platform.compiler == splitted.front) {
201 		splitted.popFront();
202 		enforce(splitted.empty, "No valid specification! The compiler has to be the last element: " ~ specification);
203 		return true;
204 	}
205 	return false;
206 }
207 
208 ///
209 unittest {
210 	auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd");
211 	assert(platform.matchesSpecification(""));
212 	assert(platform.matchesSpecification("-posix"));
213 	assert(platform.matchesSpecification("-linux"));
214 	assert(platform.matchesSpecification("-linux-dmd"));
215 	assert(platform.matchesSpecification("-linux-x86_64-dmd"));
216 	assert(platform.matchesSpecification("-x86_64"));
217 	assert(!platform.matchesSpecification("-windows"));
218 	assert(!platform.matchesSpecification("-ldc"));
219 	assert(!platform.matchesSpecification("-windows-dmd"));
220 }
221 
222 /// Represents a platform a package can be build upon.
223 struct BuildPlatform {
224 	/// Special constant used to denote matching any build platform.
225 	enum any = BuildPlatform(null, null, null, null, -1);
226 
227 	/// Platform identifiers, e.g. ["posix", "windows"]
228 	string[] platform;
229 	/// CPU architecture identifiers, e.g. ["x86", "x86_64"]
230 	string[] architecture;
231 	/// Canonical compiler name e.g. "dmd"
232 	string compiler;
233 	/// Compiler binary name e.g. "ldmd2"
234 	string compilerBinary;
235 	/// Compiled frontend version (e.g. `2067` for frontend versions 2.067.x)
236 	int frontendVersion;
237 }