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