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 return ret; 46 }; 47 48 /// private 49 enum string archCheck = q{ 50 string[] ret; 51 version(X86) ret ~= "x86"; 52 version(X86_64) ret ~= "x86_64"; 53 version(ARM) ret ~= "arm"; 54 version(ARM_Thumb) ret ~= "arm_thumb"; 55 version(ARM_SoftFloat) ret ~= "arm_softfloat"; 56 version(ARM_HardFloat) ret ~= "arm_hardfloat"; 57 version(ARM64) ret ~= "arm64"; 58 version(PPC) ret ~= "ppc"; 59 version(PPC_SoftFP) ret ~= "ppc_softfp"; 60 version(PPC_HardFP) ret ~= "ppc_hardfp"; 61 version(PPC64) ret ~= "ppc64"; 62 version(IA64) ret ~= "ia64"; 63 version(MIPS) ret ~= "mips"; 64 version(MIPS32) ret ~= "mips32"; 65 version(MIPS64) ret ~= "mips64"; 66 version(MIPS_O32) ret ~= "mips_o32"; 67 version(MIPS_N32) ret ~= "mips_n32"; 68 version(MIPS_O64) ret ~= "mips_o64"; 69 version(MIPS_N64) ret ~= "mips_n64"; 70 version(MIPS_EABI) ret ~= "mips_eabi"; 71 version(MIPS_NoFloat) ret ~= "mips_nofloat"; 72 version(MIPS_SoftFloat) ret ~= "mips_softfloat"; 73 version(MIPS_HardFloat) ret ~= "mips_hardfloat"; 74 version(SPARC) ret ~= "sparc"; 75 version(SPARC_V8Plus) ret ~= "sparc_v8plus"; 76 version(SPARC_SoftFP) ret ~= "sparc_softfp"; 77 version(SPARC_HardFP) ret ~= "sparc_hardfp"; 78 version(SPARC64) ret ~= "sparc64"; 79 version(S390) ret ~= "s390"; 80 version(S390X) ret ~= "s390x"; 81 version(HPPA) ret ~= "hppa"; 82 version(HPPA64) ret ~= "hppa64"; 83 version(SH) ret ~= "sh"; 84 version(SH64) ret ~= "sh64"; 85 version(Alpha) ret ~= "alpha"; 86 version(Alpha_SoftFP) ret ~= "alpha_softfp"; 87 version(Alpha_HardFP) ret ~= "alpha_hardfp"; 88 return ret; 89 }; 90 91 /// private 92 enum string compilerCheck = q{ 93 version(DigitalMars) return "dmd"; 94 else version(GNU) return "gdc"; 95 else version(LDC) return "ldc"; 96 else version(SDC) return "sdc"; 97 else return null; 98 }; 99 100 /** Determines the full build platform used for the current build. 101 102 Note that the `BuildPlatform.compilerBinary` field will be left empty. 103 104 See_Also: `determinePlatform`, `determineArchitecture`, `determineCompiler` 105 */ 106 BuildPlatform determineBuildPlatform() 107 { 108 BuildPlatform ret; 109 ret.platform = determinePlatform(); 110 ret.architecture = determineArchitecture(); 111 ret.compiler = determineCompiler(); 112 ret.frontendVersion = __VERSION__; 113 return ret; 114 } 115 116 117 /** Returns a list of platform identifiers that apply to the current 118 build. 119 120 Example results are `["windows"]` or `["posix", "osx"]`. The identifiers 121 correspond to the compiler defined version constants built into the 122 language, except that they are converted to lower case. 123 124 See_Also: `determineBuildPlatform` 125 */ 126 string[] determinePlatform() 127 { 128 mixin(platformCheck); 129 } 130 131 /** Returns a list of architecture identifiers that apply to the current 132 build. 133 134 Example results are `["x86_64"]` or `["arm", "arm_softfloat"]`. The 135 identifiers correspond to the compiler defined version constants built into 136 the language, except that they are converted to lower case. 137 138 See_Also: `determineBuildPlatform` 139 */ 140 string[] determineArchitecture() 141 { 142 mixin(archCheck); 143 } 144 145 /** Determines the canonical compiler name used for the current build. 146 147 The possible values currently are "dmd", "gdc", "ldc" or "sdc". If an 148 unknown compiler is used, this function will return an empty string. 149 150 See_Also: `determineBuildPlatform` 151 */ 152 string determineCompiler() 153 { 154 mixin(compilerCheck); 155 } 156 157 /** Matches a platform specification string against a build platform. 158 159 Specifications are build upon the following scheme, where each component 160 is optional (indicated by []), but the order is obligatory: 161 "[-platform][-architecture][-compiler]" 162 163 So the following strings are valid specifications: `"-windows-x86-dmd"`, 164 `"-dmd"`, `"-arm"`, `"-arm-dmd"`, `"-windows-dmd"` 165 166 Params: 167 platform = The build platform to match agains the platform specification 168 specification = The specification being matched. It must either be an 169 empty string or start with a dash. 170 171 Returns: 172 `true` if the given specification matches the build platform, `false` 173 otherwise. Using an empty string as the platform specification will 174 always result in a match. 175 */ 176 bool matchesSpecification(in BuildPlatform platform, const(char)[] specification) 177 { 178 import std.string : format; 179 import std.algorithm : canFind, splitter; 180 import std.exception : enforce; 181 182 if (specification.empty) return true; 183 if (platform == BuildPlatform.any) return true; 184 185 auto splitted = specification.splitter('-'); 186 assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!"); 187 splitted.popFront(); // Drop leading empty match. 188 enforce(!splitted.empty, format("Platform specification, if present, must not be empty: \"%s\"", specification)); 189 190 if (platform.platform.canFind(splitted.front)) { 191 splitted.popFront(); 192 if (splitted.empty) 193 return true; 194 } 195 if (platform.architecture.canFind(splitted.front)) { 196 splitted.popFront(); 197 if (splitted.empty) 198 return true; 199 } 200 if (platform.compiler == splitted.front) { 201 splitted.popFront(); 202 enforce(splitted.empty, "No valid specification! The compiler has to be the last element: " ~ specification); 203 return true; 204 } 205 return false; 206 } 207 208 /// 209 unittest { 210 auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd"); 211 assert(platform.matchesSpecification("")); 212 assert(platform.matchesSpecification("-posix")); 213 assert(platform.matchesSpecification("-linux")); 214 assert(platform.matchesSpecification("-linux-dmd")); 215 assert(platform.matchesSpecification("-linux-x86_64-dmd")); 216 assert(platform.matchesSpecification("-x86_64")); 217 assert(!platform.matchesSpecification("-windows")); 218 assert(!platform.matchesSpecification("-ldc")); 219 assert(!platform.matchesSpecification("-windows-dmd")); 220 } 221 222 /// Represents a platform a package can be build upon. 223 struct BuildPlatform { 224 /// Special constant used to denote matching any build platform. 225 enum any = BuildPlatform(null, null, null, null, -1); 226 227 /// Platform identifiers, e.g. ["posix", "windows"] 228 string[] platform; 229 /// CPU architecture identifiers, e.g. ["x86", "x86_64"] 230 string[] architecture; 231 /// Canonical compiler name e.g. "dmd" 232 string compiler; 233 /// Compiler binary name e.g. "ldmd2" 234 string compilerBinary; 235 /// Compiled frontend version (e.g. `2067` for frontend versions 2.067.x) 236 int frontendVersion; 237 }