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