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", "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 unnesessary 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 agains 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 : 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.splitter('-'); 199 assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!"); 200 splitted.popFront(); // Drop leading empty match. 201 enforce(!splitted.empty, format("Platform specification, if present, must not be empty: \"%s\"", specification)); 202 203 if (platform.platform.canFind(splitted.front)) { 204 splitted.popFront(); 205 if (splitted.empty) 206 return true; 207 } 208 if (platform.architecture.canFind(splitted.front)) { 209 splitted.popFront(); 210 if (splitted.empty) 211 return true; 212 } 213 if (platform.compiler == splitted.front) { 214 splitted.popFront(); 215 enforce(splitted.empty, "No valid specification! The compiler has to be the last element: " ~ specification); 216 return true; 217 } 218 return false; 219 } 220 221 /// 222 unittest { 223 auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd"); 224 assert(platform.matchesSpecification("")); 225 assert(platform.matchesSpecification("-posix")); 226 assert(platform.matchesSpecification("-linux")); 227 assert(platform.matchesSpecification("-linux-dmd")); 228 assert(platform.matchesSpecification("-linux-x86_64-dmd")); 229 assert(platform.matchesSpecification("-x86_64")); 230 assert(!platform.matchesSpecification("-windows")); 231 assert(!platform.matchesSpecification("-ldc")); 232 assert(!platform.matchesSpecification("-windows-dmd")); 233 } 234 235 /// Represents a platform a package can be build upon. 236 struct BuildPlatform { 237 /// Special constant used to denote matching any build platform. 238 enum any = BuildPlatform(null, null, null, null, -1); 239 240 /// Platform identifiers, e.g. ["posix", "windows"] 241 string[] platform; 242 /// CPU architecture identifiers, e.g. ["x86", "x86_64"] 243 string[] architecture; 244 /// Canonical compiler name e.g. "dmd" 245 string compiler; 246 /// Compiler binary name e.g. "ldmd2" 247 string compilerBinary; 248 /// Compiled frontend version (e.g. `2067` for frontend versions 2.067.x) 249 int frontendVersion; 250 /// Compiler version e.g. "1.11.0" 251 string compilerVersion; 252 /// Frontend version string from frontendVersion 253 /// e.g: 2067 => "2.067" 254 string frontendVersionString() const 255 { 256 import std.format : format; 257 258 const maj = frontendVersion / 1000; 259 const min = frontendVersion % 1000; 260 return format("%d.%03d", maj, min); 261 } 262 /// 263 unittest 264 { 265 BuildPlatform bp; 266 bp.frontendVersion = 2067; 267 assert(bp.frontendVersionString == "2.067"); 268 } 269 } 270 271