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