1 /** 2 Abstract representation of a package description file. 3 4 Copyright: © 2012-2014 rejectedsoftware e.K. 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig, Matthias Dondorff 7 */ 8 module dub.recipe.packagerecipe; 9 10 import dub.compilers.compiler; 11 import dub.compilers.utils : warnOnSpecialCompilerFlags; 12 import dub.dependency; 13 14 import dub.internal.vibecompat.core.file; 15 import dub.internal.vibecompat.core.log; 16 import dub.internal.vibecompat.inet.url; 17 18 import std.algorithm : findSplit, sort; 19 import std.array : join, split; 20 import std.exception : enforce; 21 import std.file; 22 import std.range; 23 24 25 /** 26 Returns the individual parts of a qualified package name. 27 28 Sub qualified package names are lists of package names separated by ":". For 29 example, "packa:packb:packc" references a package named "packc" that is a 30 sub package of "packb", wich in turn is a sub package of "packa". 31 */ 32 string[] getSubPackagePath(string package_name) 33 { 34 return package_name.split(":"); 35 } 36 37 /** 38 Returns the name of the top level package for a given (sub) package name. 39 40 In case of a top level package, the qualified name is returned unmodified. 41 */ 42 string getBasePackageName(string package_name) 43 { 44 return package_name.findSplit(":")[0]; 45 } 46 47 /** 48 Returns the qualified sub package part of the given package name. 49 50 This is the part of the package name excluding the base package 51 name. See also $(D getBasePackageName). 52 */ 53 string getSubPackageName(string package_name) 54 { 55 return package_name.findSplit(":")[2]; 56 } 57 58 unittest 59 { 60 assert(getSubPackagePath("packa:packb:packc") == ["packa", "packb", "packc"]); 61 assert(getSubPackagePath("pack") == ["pack"]); 62 assert(getBasePackageName("packa:packb:packc") == "packa"); 63 assert(getBasePackageName("pack") == "pack"); 64 assert(getSubPackageName("packa:packb:packc") == "packb:packc"); 65 assert(getSubPackageName("pack") == ""); 66 } 67 68 /** 69 Represents the contents of a package recipe file (dub.json/dub.sdl) in an abstract way. 70 71 This structure is used to reason about package descriptions in isolation. 72 For higher level package handling, see the $(D Package) class. 73 */ 74 struct PackageRecipe { 75 string name; 76 string version_; 77 string description; 78 string homepage; 79 string[] authors; 80 string copyright; 81 string license; 82 string[] ddoxFilterArgs; 83 string ddoxTool; 84 BuildSettingsTemplate buildSettings; 85 ConfigurationInfo[] configurations; 86 BuildSettingsTemplate[string] buildTypes; 87 88 SubPackage[] subPackages; 89 90 inout(ConfigurationInfo) getConfiguration(string name) 91 inout { 92 foreach (c; configurations) 93 if (c.name == name) 94 return c; 95 throw new Exception("Unknown configuration: "~name); 96 } 97 98 /** Clones the package recipe recursively. 99 */ 100 PackageRecipe clone() const { return .clone(this); } 101 } 102 103 struct SubPackage 104 { 105 string path; 106 PackageRecipe recipe; 107 } 108 109 110 /// Bundles information about a build configuration. 111 struct ConfigurationInfo { 112 string name; 113 string[] platforms; 114 BuildSettingsTemplate buildSettings; 115 116 this(string name, BuildSettingsTemplate build_settings) 117 { 118 enforce(!name.empty, "Configuration name is empty."); 119 this.name = name; 120 this.buildSettings = build_settings; 121 } 122 123 bool matchesPlatform(in BuildPlatform platform) 124 const { 125 if( platforms.empty ) return true; 126 foreach(p; platforms) 127 if( platform.matchesSpecification("-"~p) ) 128 return true; 129 return false; 130 } 131 } 132 133 /// This keeps general information about how to build a package. 134 /// It contains functions to create a specific BuildSetting, targeted at 135 /// a certain BuildPlatform. 136 struct BuildSettingsTemplate { 137 Dependency[string] dependencies; 138 string systemDependencies; 139 TargetType targetType = TargetType.autodetect; 140 string targetPath; 141 string targetName; 142 string workingDirectory; 143 string mainSourceFile; 144 string[string] subConfigurations; 145 string[][string] dflags; 146 string[][string] lflags; 147 string[][string] libs; 148 string[][string] sourceFiles; 149 string[][string] sourcePaths; 150 string[][string] excludedSourceFiles; 151 string[][string] copyFiles; 152 string[][string] versions; 153 string[][string] debugVersions; 154 string[][string] importPaths; 155 string[][string] stringImportPaths; 156 string[][string] preGenerateCommands; 157 string[][string] postGenerateCommands; 158 string[][string] preBuildCommands; 159 string[][string] postBuildCommands; 160 BuildRequirements[string] buildRequirements; 161 BuildOptions[string] buildOptions; 162 163 164 /// Constructs a BuildSettings object from this template. 165 void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, Path base_path) 166 const { 167 dst.targetType = this.targetType; 168 if (!this.targetPath.empty) dst.targetPath = this.targetPath; 169 if (!this.targetName.empty) dst.targetName = this.targetName; 170 if (!this.workingDirectory.empty) dst.workingDirectory = this.workingDirectory; 171 if (!this.mainSourceFile.empty) { 172 dst.mainSourceFile = this.mainSourceFile; 173 dst.addSourceFiles(this.mainSourceFile); 174 } 175 176 void collectFiles(string method)(in string[][string] paths_map, string pattern) 177 { 178 foreach (suffix, paths; paths_map) { 179 if (!platform.matchesSpecification(suffix)) 180 continue; 181 182 foreach (spath; paths) { 183 enforce(!spath.empty, "Paths must not be empty strings."); 184 auto path = Path(spath); 185 if (!path.absolute) path = base_path ~ path; 186 if (!existsFile(path) || !isDir(path.toNativeString())) { 187 logWarn("Invalid source/import path: %s", path.toNativeString()); 188 continue; 189 } 190 191 foreach (d; dirEntries(path.toNativeString(), pattern, SpanMode.depth)) { 192 import std.path : baseName; 193 if (baseName(d.name)[0] == '.' || isDir(d.name)) continue; 194 auto src = Path(d.name).relativeTo(base_path); 195 __traits(getMember, dst, method)(src.toNativeString()); 196 } 197 } 198 } 199 } 200 201 // collect files from all source/import folders 202 collectFiles!"addSourceFiles"(sourcePaths, "*.d"); 203 collectFiles!"addImportFiles"(importPaths, "*.{d,di}"); 204 dst.removeImportFiles(dst.sourceFiles); 205 collectFiles!"addStringImportFiles"(stringImportPaths, "*"); 206 207 // ensure a deterministic order of files as passed to the compiler 208 dst.sourceFiles.sort(); 209 210 getPlatformSetting!("dflags", "addDFlags")(dst, platform); 211 getPlatformSetting!("lflags", "addLFlags")(dst, platform); 212 getPlatformSetting!("libs", "addLibs")(dst, platform); 213 getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform); 214 getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform); 215 getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform); 216 getPlatformSetting!("versions", "addVersions")(dst, platform); 217 getPlatformSetting!("debugVersions", "addDebugVersions")(dst, platform); 218 getPlatformSetting!("importPaths", "addImportPaths")(dst, platform); 219 getPlatformSetting!("stringImportPaths", "addStringImportPaths")(dst, platform); 220 getPlatformSetting!("preGenerateCommands", "addPreGenerateCommands")(dst, platform); 221 getPlatformSetting!("postGenerateCommands", "addPostGenerateCommands")(dst, platform); 222 getPlatformSetting!("preBuildCommands", "addPreBuildCommands")(dst, platform); 223 getPlatformSetting!("postBuildCommands", "addPostBuildCommands")(dst, platform); 224 getPlatformSetting!("buildRequirements", "addRequirements")(dst, platform); 225 getPlatformSetting!("buildOptions", "addOptions")(dst, platform); 226 } 227 228 void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform) 229 const { 230 foreach(suffix, values; __traits(getMember, this, name)){ 231 if( platform.matchesSpecification(suffix) ) 232 __traits(getMember, dst, addname)(values); 233 } 234 } 235 236 void warnOnSpecialCompilerFlags(string package_name, string config_name) 237 { 238 auto nodef = false; 239 auto noprop = false; 240 foreach (req; this.buildRequirements) { 241 if (req & BuildRequirement.noDefaultFlags) nodef = true; 242 if (req & BuildRequirement.relaxProperties) noprop = true; 243 } 244 245 if (noprop) { 246 logWarn(`Warning: "buildRequirements": ["relaxProperties"] is deprecated and is now the default behavior. Note that the -property switch will probably be removed in future versions of DMD.`); 247 logWarn(""); 248 } 249 250 if (nodef) { 251 logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages."); 252 logWarn(""); 253 } else { 254 string[] all_dflags; 255 BuildOptions all_options; 256 foreach (flags; this.dflags) all_dflags ~= flags; 257 foreach (options; this.buildOptions) all_options |= options; 258 .warnOnSpecialCompilerFlags(all_dflags, all_options, package_name, config_name); 259 } 260 } 261 } 262 263 private T clone(T)(ref const(T) val) 264 { 265 import std.traits : isSomeString, isDynamicArray, isAssociativeArray, isBasicType, ValueType; 266 267 static if (is(T == immutable)) return val; 268 else static if (isBasicType!T) return val; 269 else static if (isDynamicArray!T) { 270 alias V = typeof(T.init[0]); 271 static if (is(V == immutable)) return val; 272 else { 273 T ret = new V[val.length]; 274 foreach (i, ref f; val) 275 ret[i] = clone!V(f); 276 return ret; 277 } 278 } else static if (isAssociativeArray!T) { 279 alias V = ValueType!T; 280 T ret; 281 foreach (k, ref f; val) 282 ret[k] = clone!V(f); 283 return ret; 284 } else static if (is(T == struct)) { 285 T ret; 286 foreach (i, M; typeof(T.tupleof)) 287 ret.tupleof[i] = clone!M(val.tupleof[i]); 288 return ret; 289 } else static assert(false, "Unsupported type: "~T.stringof); 290 }