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