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", which 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, NativePath 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 string[] collectFiles(in string[][string] paths_map, string pattern) 177 { 178 auto files = appender!(string[]); 179 180 foreach (suffix, paths; paths_map) { 181 if (!platform.matchesSpecification(suffix)) 182 continue; 183 184 foreach (spath; paths) { 185 enforce(!spath.empty, "Paths must not be empty strings."); 186 auto path = NativePath(spath); 187 if (!path.absolute) path = base_path ~ path; 188 if (!existsFile(path) || !isDir(path.toNativeString())) { 189 logWarn("Invalid source/import path: %s", path.toNativeString()); 190 continue; 191 } 192 193 foreach (d; dirEntries(path.toNativeString(), pattern, SpanMode.depth)) { 194 import std.path : baseName; 195 if (baseName(d.name)[0] == '.' || d.isDir) continue; 196 auto src = NativePath(d.name).relativeTo(base_path); 197 files ~= src.toNativeString(); 198 } 199 } 200 } 201 202 return files.data; 203 } 204 205 // collect source files 206 dst.addSourceFiles(collectFiles(sourcePaths, "*.d")); 207 auto sourceFiles = dst.sourceFiles.sort(); 208 209 // collect import files and remove sources 210 import std.algorithm : copy, setDifference; 211 212 auto importFiles = collectFiles(importPaths, "*.{d,di}").sort(); 213 immutable nremoved = importFiles.setDifference(sourceFiles).copy(importFiles.release).length; 214 importFiles = importFiles[0 .. $ - nremoved]; 215 dst.addImportFiles(importFiles.release); 216 217 dst.addStringImportFiles(collectFiles(stringImportPaths, "*")); 218 219 getPlatformSetting!("dflags", "addDFlags")(dst, platform); 220 getPlatformSetting!("lflags", "addLFlags")(dst, platform); 221 getPlatformSetting!("libs", "addLibs")(dst, platform); 222 getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform); 223 getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform); 224 getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform); 225 getPlatformSetting!("versions", "addVersions")(dst, platform); 226 getPlatformSetting!("debugVersions", "addDebugVersions")(dst, platform); 227 getPlatformSetting!("importPaths", "addImportPaths")(dst, platform); 228 getPlatformSetting!("stringImportPaths", "addStringImportPaths")(dst, platform); 229 getPlatformSetting!("preGenerateCommands", "addPreGenerateCommands")(dst, platform); 230 getPlatformSetting!("postGenerateCommands", "addPostGenerateCommands")(dst, platform); 231 getPlatformSetting!("preBuildCommands", "addPreBuildCommands")(dst, platform); 232 getPlatformSetting!("postBuildCommands", "addPostBuildCommands")(dst, platform); 233 getPlatformSetting!("buildRequirements", "addRequirements")(dst, platform); 234 getPlatformSetting!("buildOptions", "addOptions")(dst, platform); 235 } 236 237 void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform) 238 const { 239 foreach(suffix, values; __traits(getMember, this, name)){ 240 if( platform.matchesSpecification(suffix) ) 241 __traits(getMember, dst, addname)(values); 242 } 243 } 244 245 void warnOnSpecialCompilerFlags(string package_name, string config_name) 246 { 247 auto nodef = false; 248 auto noprop = false; 249 foreach (req; this.buildRequirements) { 250 if (req & BuildRequirement.noDefaultFlags) nodef = true; 251 if (req & BuildRequirement.relaxProperties) noprop = true; 252 } 253 254 if (noprop) { 255 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.`); 256 logWarn(""); 257 } 258 259 if (nodef) { 260 logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages."); 261 logWarn(""); 262 } else { 263 string[] all_dflags; 264 BuildOptions all_options; 265 foreach (flags; this.dflags) all_dflags ~= flags; 266 foreach (options; this.buildOptions) all_options |= options; 267 .warnOnSpecialCompilerFlags(all_dflags, all_options, package_name, config_name); 268 } 269 } 270 } 271 272 private T clone(T)(ref const(T) val) 273 { 274 import std.traits : isSomeString, isDynamicArray, isAssociativeArray, isBasicType, ValueType; 275 276 static if (is(T == immutable)) return val; 277 else static if (isBasicType!T) return val; 278 else static if (isDynamicArray!T) { 279 alias V = typeof(T.init[0]); 280 static if (is(V == immutable)) return val; 281 else { 282 T ret = new V[val.length]; 283 foreach (i, ref f; val) 284 ret[i] = clone!V(f); 285 return ret; 286 } 287 } else static if (isAssociativeArray!T) { 288 alias V = ValueType!T; 289 T ret; 290 foreach (k, ref f; val) 291 ret[k] = clone!V(f); 292 return ret; 293 } else static if (is(T == struct)) { 294 T ret; 295 foreach (i, M; typeof(T.tupleof)) 296 ret.tupleof[i] = clone!M(val.tupleof[i]); 297 return ret; 298 } else static assert(false, "Unsupported type: "~T.stringof); 299 }