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 return ret; 102 }; 103 104 /// private 105 enum string compilerCheck = q{ 106 version(DigitalMars) return "dmd"; 107 else version(GNU) return "gdc"; 108 else version(LDC) return "ldc"; 109 else version(SDC) return "sdc"; 110 else return null; 111 }; 112 113 /** Determines the full build platform used for the current build. 114 115 Note that the `BuildPlatform.compilerBinary` field will be left empty. 116 117 See_Also: `determinePlatform`, `determineArchitecture`, `determineCompiler` 118 */ 119 BuildPlatform determineBuildPlatform() 120 { 121 BuildPlatform ret; 122 ret.platform = determinePlatform(); 123 ret.architecture = determineArchitecture(); 124 ret.compiler = determineCompiler(); 125 ret.frontendVersion = __VERSION__; 126 return ret; 127 } 128 129 130 /** Returns a list of platform identifiers that apply to the current 131 build. 132 133 Example results are `["windows"]` or `["posix", "osx"]`. The identifiers 134 correspond to the compiler defined version constants built into the 135 language, except that they are converted to lower case. 136 137 See_Also: `determineBuildPlatform` 138 */ 139 string[] determinePlatform() 140 { 141 mixin(platformCheck); 142 } 143 144 /** Returns a list of architecture identifiers that apply to the current 145 build. 146 147 Example results are `["x86_64"]` or `["arm", "arm_softfloat"]`. The 148 identifiers correspond to the compiler defined version constants built into 149 the language, except that they are converted to lower case. 150 151 See_Also: `determineBuildPlatform` 152 */ 153 string[] determineArchitecture() 154 { 155 mixin(archCheck); 156 } 157 158 /** Determines the canonical compiler name used for the current build. 159 160 The possible values currently are "dmd", "gdc", "ldc" or "sdc". If an 161 unknown compiler is used, this function will return an empty string. 162 163 See_Also: `determineBuildPlatform` 164 */ 165 string determineCompiler() 166 { 167 mixin(compilerCheck); 168 } 169 170 /** Matches a platform specification string against a build platform. 171 172 Specifications are build upon the following scheme, where each component 173 is optional (indicated by []), but the order is obligatory: 174 "[-platform][-architecture][-compiler]" 175 176 So the following strings are valid specifications: `"-windows-x86-dmd"`, 177 `"-dmd"`, `"-arm"`, `"-arm-dmd"`, `"-windows-dmd"` 178 179 Params: 180 platform = The build platform to match against the platform specification 181 specification = The specification being matched. It must either be an 182 empty string or start with a dash. 183 184 Returns: 185 `true` if the given specification matches the build platform, `false` 186 otherwise. Using an empty string as the platform specification will 187 always result in a match. 188 */ 189 bool matchesSpecification(in BuildPlatform platform, const(char)[] specification) 190 { 191 import std.string : chompPrefix, format; 192 import std.algorithm : canFind, splitter; 193 import std.exception : enforce; 194 195 if (specification.empty) return true; 196 if (platform == BuildPlatform.any) return true; 197 198 auto splitted = specification.chompPrefix("-").splitter('-'); 199 enforce(!splitted.empty, format("Platform specification, if present, must not be empty: \"%s\"", specification)); 200 201 if (platform.platform.canFind(splitted.front)) { 202 splitted.popFront(); 203 if (splitted.empty) 204 return true; 205 } 206 if (platform.architecture.canFind(splitted.front)) { 207 splitted.popFront(); 208 if (splitted.empty) 209 return true; 210 } 211 if (platform.compiler == splitted.front) { 212 splitted.popFront(); 213 enforce(splitted.empty, "No valid specification! The compiler has to be the last element: " ~ specification); 214 return true; 215 } 216 return false; 217 } 218 219 /// 220 unittest { 221 auto platform = BuildPlatform(["posix", "linux"], ["x86_64"], "dmd"); 222 assert(platform.matchesSpecification("")); 223 assert(platform.matchesSpecification("posix")); 224 assert(platform.matchesSpecification("linux")); 225 assert(platform.matchesSpecification("linux-dmd")); 226 assert(platform.matchesSpecification("linux-x86_64-dmd")); 227 assert(platform.matchesSpecification("x86_64")); 228 assert(!platform.matchesSpecification("windows")); 229 assert(!platform.matchesSpecification("ldc")); 230 assert(!platform.matchesSpecification("windows-dmd")); 231 232 // Before PR#2279, a leading '-' was required 233 assert(platform.matchesSpecification("-x86_64")); 234 } 235 236 /// Represents a platform a package can be build upon. 237 struct BuildPlatform { 238 /// Special constant used to denote matching any build platform. 239 enum any = BuildPlatform(null, null, null, null, -1); 240 241 /// Platform identifiers, e.g. ["posix", "windows"] 242 string[] platform; 243 /// CPU architecture identifiers, e.g. ["x86", "x86_64"] 244 string[] architecture; 245 /// Canonical compiler name e.g. "dmd" 246 string compiler; 247 /// Compiler binary name e.g. "ldmd2" 248 string compilerBinary; 249 /// Compiled frontend version (e.g. `2067` for frontend versions 2.067.x) 250 int frontendVersion; 251 /// Compiler version e.g. "1.11.0" 252 string compilerVersion; 253 /// Frontend version string from frontendVersion 254 /// e.g: 2067 => "2.067" 255 string frontendVersionString() const 256 { 257 import std.format : format; 258 259 const maj = frontendVersion / 1000; 260 const min = frontendVersion % 1000; 261 return format("%d.%03d", maj, min); 262 } 263 /// 264 unittest 265 { 266 BuildPlatform bp; 267 bp.frontendVersion = 2067; 268 assert(bp.frontendVersionString == "2.067"); 269 } 270 271 /// Checks to see if platform field contains windows 272 bool isWindows() const { 273 import std.algorithm : canFind; 274 return this.platform.canFind("windows"); 275 } 276 /// 277 unittest { 278 BuildPlatform bp; 279 bp.platform = ["windows"]; 280 assert(bp.isWindows); 281 bp.platform = ["posix"]; 282 assert(!bp.isWindows); 283 } 284 }