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 }