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 recipes. 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", "darwin"]; 30 version(iOS) ret ~= ["ios", "darwin"]; 31 version(TVOS) ret ~= ["tvos", "darwin"]; 32 version(WatchOS) ret ~= ["watchos", "darwin"]; 33 version(FreeBSD) ret ~= "freebsd"; 34 version(OpenBSD) ret ~= "openbsd"; 35 version(NetBSD) ret ~= "netbsd"; 36 version(DragonFlyBSD) ret ~= "dragonflybsd"; 37 version(BSD) ret ~= "bsd"; 38 version(Solaris) ret ~= "solaris"; 39 version(AIX) ret ~= "aix"; 40 version(Haiku) ret ~= "haiku"; 41 version(SkyOS) ret ~= "skyos"; 42 version(SysV3) ret ~= "sysv3"; 43 version(SysV4) ret ~= "sysv4"; 44 version(Hurd) ret ~= "hurd"; 45 version(Android) ret ~= "android"; 46 version(Cygwin) ret ~= "cygwin"; 47 version(MinGW) ret ~= "mingw"; 48 version(PlayStation4) ret ~= "playstation4"; 49 version(WebAssembly) ret ~= "wasm"; 50 version(Emscripten) ret ~= "emscripten"; 51 return ret; 52 }; 53 54 /// private 55 enum string archCheck = q{ 56 string[] ret; 57 version(X86) ret ~= "x86"; 58 // Hack: see #1535 59 // Makes "x86_omf" available as a platform specifier in the package recipe 60 version(X86) version(CRuntime_DigitalMars) ret ~= "x86_omf"; 61 // Hack: see #1059 62 // When compiling with --arch=x86_mscoff build_platform.architecture is equal to ["x86"] and canFind below is false. 63 // This hack prevents unnecessary warning 'Failed to apply the selected architecture x86_mscoff. Got ["x86"]'. 64 // And also makes "x86_mscoff" available as a platform specifier in the package recipe 65 version(X86) version(CRuntime_Microsoft) ret ~= "x86_mscoff"; 66 version(X86_64) ret ~= "x86_64"; 67 version(ARM) ret ~= "arm"; 68 version(AArch64) ret ~= "aarch64"; 69 version(ARM_Thumb) ret ~= "arm_thumb"; 70 version(ARM_SoftFloat) ret ~= "arm_softfloat"; 71 version(ARM_HardFloat) ret ~= "arm_hardfloat"; 72 version(PPC) ret ~= "ppc"; 73 version(PPC_SoftFP) ret ~= "ppc_softfp"; 74 version(PPC_HardFP) ret ~= "ppc_hardfp"; 75 version(PPC64) ret ~= "ppc64"; 76 version(IA64) ret ~= "ia64"; 77 version(MIPS) ret ~= "mips"; 78 version(MIPS32) ret ~= "mips32"; 79 version(MIPS64) ret ~= "mips64"; 80 version(MIPS_O32) ret ~= "mips_o32"; 81 version(MIPS_N32) ret ~= "mips_n32"; 82 version(MIPS_O64) ret ~= "mips_o64"; 83 version(MIPS_N64) ret ~= "mips_n64"; 84 version(MIPS_EABI) ret ~= "mips_eabi"; 85 version(MIPS_NoFloat) ret ~= "mips_nofloat"; 86 version(MIPS_SoftFloat) ret ~= "mips_softfloat"; 87 version(MIPS_HardFloat) ret ~= "mips_hardfloat"; 88 version(SPARC) ret ~= "sparc"; 89 version(SPARC_V8Plus) ret ~= "sparc_v8plus"; 90 version(SPARC_SoftFP) ret ~= "sparc_softfp"; 91 version(SPARC_HardFP) ret ~= "sparc_hardfp"; 92 version(SPARC64) ret ~= "sparc64"; 93 version(S390) ret ~= "s390"; 94 version(S390X) ret ~= "s390x"; 95 version(HPPA) ret ~= "hppa"; 96 version(HPPA64) ret ~= "hppa64"; 97 version(SH) ret ~= "sh"; 98 version(SH64) ret ~= "sh64"; 99 version(Alpha) ret ~= "alpha"; 100 version(Alpha_SoftFP) ret ~= "alpha_softfp"; 101 version(Alpha_HardFP) ret ~= "alpha_hardfp"; 102 version(LoongArch32) ret ~= "loongarch32"; 103 version(LoongArch64) ret ~= "loongarch64"; 104 version(LoongArch_SoftFloat) ret ~= "loongarch_softfloat"; 105 version(LoongArch_HardFloat) ret ~= "loongarch_hardfloat"; 106 version(WebAssembly) { 107 version(D_LP64) ret ~= "wasm64"; 108 else ret ~= "wasm32"; 109 } 110 return ret; 111 }; 112 113 /// private 114 enum string compilerCheck = q{ 115 version(DigitalMars) return "dmd"; 116 else version(GNU) return "gdc"; 117 else version(LDC) return "ldc"; 118 else version(SDC) return "sdc"; 119 else return null; 120 }; 121 122 /// private 123 enum string compilerCheckPragmas = q{ 124 version(DigitalMars) pragma(msg, ` "dmd"`); 125 else version(GNU) pragma(msg, ` "gdc"`); 126 else version(LDC) pragma(msg, ` "ldc"`); 127 else version(SDC) pragma(msg, ` "sdc"`); 128 }; 129 130 /// private, converts the above appender strings to pragmas 131 string pragmaGen(string str) { 132 import std.string : replace; 133 return str.replace("return ret;", "").replace("string[] ret;", "").replace(`["`, `"`).replace(`", "`,`" "`).replace(`"]`, `"`).replace(`;`, "`);").replace("ret ~= ", "pragma(msg, ` "); 134 } 135 136 137 /** Determines the full build platform used for the current build. 138 139 Note that the `BuildPlatform.compilerBinary` field will be left empty. 140 141 See_Also: `determinePlatform`, `determineArchitecture`, `determineCompiler` 142 */ 143 BuildPlatform determineBuildPlatform() 144 { 145 BuildPlatform ret; 146 ret.platform = determinePlatform(); 147 ret.architecture = determineArchitecture(); 148 ret.compiler = determineCompiler(); 149 ret.frontendVersion = __VERSION__; 150 return ret; 151 } 152 153 154 /** Returns a list of platform identifiers that apply to the current 155 build. 156 157 Example results are `["windows"]` or `["posix", "osx"]`. The identifiers 158 correspond to the compiler defined version constants built into the 159 language, except that they are converted to lower case. 160 161 See_Also: `determineBuildPlatform` 162 */ 163 string[] determinePlatform() 164 { 165 mixin(platformCheck); 166 } 167 168 /** Returns a list of architecture identifiers that apply to the current 169 build. 170 171 Example results are `["x86_64"]` or `["arm", "arm_softfloat"]`. The 172 identifiers correspond to the compiler defined version constants built into 173 the language, except that they are converted to lower case. 174 175 See_Also: `determineBuildPlatform` 176 */ 177 string[] determineArchitecture() 178 { 179 mixin(archCheck); 180 } 181 182 /** Determines the canonical compiler name used for the current build. 183 184 The possible values currently are "dmd", "gdc", "ldc" or "sdc". If an 185 unknown compiler is used, this function will return an empty string. 186 187 See_Also: `determineBuildPlatform` 188 */ 189 string determineCompiler() 190 { 191 mixin(compilerCheck); 192 } 193 194 /** Matches a platform specification string against a build platform. 195 196 Specifications are build upon the following scheme, where each component 197 is optional (indicated by []), but the order is obligatory: 198 "[-platform][-architecture][-compiler]" 199 200 So the following strings are valid specifications: `"-windows-x86-dmd"`, 201 `"-dmd"`, `"-arm"`, `"-arm-dmd"`, `"-windows-dmd"` 202 203 Params: 204 platform = The build platform to match against the platform specification 205 specification = The specification being matched. It must either be an 206 empty string or start with a dash. 207 208 Returns: 209 `true` if the given specification matches the build platform, `false` 210 otherwise. Using an empty string as the platform specification will 211 always result in a match. 212 */ 213 bool matchesSpecification(in BuildPlatform platform, const(char)[] specification) 214 { 215 import std.string : chompPrefix, format; 216 import std.algorithm : canFind, splitter; 217 import std.exception : enforce; 218 219 if (specification.empty) return true; 220 if (platform == BuildPlatform.any) return true; 221 222 auto splitted = specification.chompPrefix("-").splitter('-'); 223 enforce(!splitted.empty, format("Platform specification, if present, must not be empty: \"%s\"", specification)); 224 225 if (platform.platform.canFind(splitted.front)) { 226 splitted.popFront(); 227 if (splitted.empty) 228 return true; 229 } 230 if (platform.architecture.canFind(splitted.front)) { 231 splitted.popFront(); 232 if (splitted.empty) 233 return true; 234 } 235 if (platform.compiler == splitted.front) { 236 splitted.popFront(); 237 enforce(splitted.empty, "No valid specification! The compiler has to be the last element: " ~ specification); 238 return true; 239 } 240 return false; 241 } 242 243 /// 244 unittest { 245 auto platform = BuildPlatform(["posix", "linux"], ["x86_64"], "dmd"); 246 assert(platform.matchesSpecification("")); 247 assert(platform.matchesSpecification("posix")); 248 assert(platform.matchesSpecification("linux")); 249 assert(platform.matchesSpecification("linux-dmd")); 250 assert(platform.matchesSpecification("linux-x86_64-dmd")); 251 assert(platform.matchesSpecification("x86_64")); 252 assert(!platform.matchesSpecification("windows")); 253 assert(!platform.matchesSpecification("ldc")); 254 assert(!platform.matchesSpecification("windows-dmd")); 255 256 // Before PR#2279, a leading '-' was required 257 assert(platform.matchesSpecification("-x86_64")); 258 } 259 260 /// Represents a platform a package can be build upon. 261 struct BuildPlatform { 262 /// Special constant used to denote matching any build platform. 263 enum any = BuildPlatform(null, null, null, null, -1); 264 265 /// Platform identifiers, e.g. ["posix", "windows"] 266 string[] platform; 267 /// CPU architecture identifiers, e.g. ["x86", "x86_64"] 268 string[] architecture; 269 /// Canonical compiler name e.g. "dmd" 270 string compiler; 271 /// Compiler binary name e.g. "ldmd2" 272 string compilerBinary; 273 /// Compiled frontend version (e.g. `2067` for frontend versions 2.067.x) 274 int frontendVersion; 275 /// Compiler version e.g. "1.11.0" 276 string compilerVersion; 277 /// Frontend version string from frontendVersion 278 /// e.g: 2067 => "2.067" 279 string frontendVersionString() const 280 { 281 import std.format : format; 282 283 const maj = frontendVersion / 1000; 284 const min = frontendVersion % 1000; 285 return format("%d.%03d", maj, min); 286 } 287 /// 288 unittest 289 { 290 BuildPlatform bp; 291 bp.frontendVersion = 2067; 292 assert(bp.frontendVersionString == "2.067"); 293 } 294 295 /// Checks to see if platform field contains windows 296 bool isWindows() const { 297 import std.algorithm : canFind; 298 return this.platform.canFind("windows"); 299 } 300 /// 301 unittest { 302 BuildPlatform bp; 303 bp.platform = ["windows"]; 304 assert(bp.isWindows); 305 bp.platform = ["posix"]; 306 assert(!bp.isWindows); 307 } 308 309 /// Checks to see if platform field contains darwin 310 bool isDarwin() const { 311 import std.algorithm : canFind; 312 return this.platform.canFind("darwin"); 313 } 314 /// 315 unittest { 316 BuildPlatform bp; 317 bp.platform = ["osx", "darwin"]; 318 assert(bp.isDarwin); 319 bp.platform = ["posix"]; 320 assert(!bp.isDarwin); 321 } 322 }