1 module dud.pkgdescription.platformselection;
2 
3 import std.algorithm.iteration : each, map, filter;
4 import std.algorithm.searching : canFind, all, any;
5 import std.algorithm.sorting : sort;
6 import std.array : array, empty, front;
7 import std.exception : enforce;
8 import std.format;
9 import std.range : tee;
10 import std.stdio;
11 import std.traits : FieldNameTuple;
12 import std.typecons : Nullable, apply, nullable;
13 
14 import dud.pkgdescription.duplicate : ddup = dup;
15 import dud.pkgdescription.exception;
16 import dud.pkgdescription.path;
17 import dud.pkgdescription;
18 import dud.semver.semver;
19 import dud.semver.versionrange;
20 
21 PackageDescriptionNoPlatform select()(const(PackageDescription) pkg,
22 		const(Platform[]) platform)
23 {
24 	return selectImpl(pkg, platform);
25 }
26 
27 struct PackageDescriptionNoPlatform {
28 @safe pure:
29 	string name;
30 
31 	SemVer version_;
32 
33 	string description;
34 
35 	string homepage;
36 
37 	string[] authors;
38 
39 	string copyright;
40 
41 	string license;
42 
43 	string systemDependencies;
44 
45 	DependencyNoPlatform[string] dependencies;
46 
47 	TargetType targetType;
48 
49 	UnprocessedPath targetPath;
50 
51 	string targetName;
52 
53 	UnprocessedPath workingDirectory;
54 
55 	UnprocessedPath mainSourceFile;
56 
57 	string[] dflags; /// Flags passed to the D compiler
58 
59 	string[] lflags; /// Flags passed to the linker
60 
61 	string[] libs; /// Librariy names to link against (typically using "-l<name>")
62 
63 	UnprocessedPath[] copyFiles; /// Files to copy to the target directory
64 
65 	string[] versions; /// D version identifiers to set
66 
67 	string[] debugVersions; /// D debug version identifiers to set
68 
69 	UnprocessedPath[] importPaths;
70 
71 	UnprocessedPath[] sourcePaths;
72 
73 	UnprocessedPath[] sourceFiles;
74 
75 	UnprocessedPath[] excludedSourceFiles;
76 
77 	UnprocessedPath[] stringImportPaths;
78 
79 	string[] preGenerateCommands; /// commands executed before creating the description
80 
81 	string[] postGenerateCommands; /// commands executed after creating the description
82 
83 	string[] preBuildCommands; /// Commands to execute prior to every build
84 
85 	string[] postBuildCommands; /// Commands to execute after every build
86 
87 	string[] preRunCommands; /// Commands to execute prior to every run
88 
89 	string[] postRunCommands; /// Commands to execute after every run
90 
91 	string[] ddoxFilterArgs;
92 
93 	string[] debugVersionFilters;
94 
95 	string ddoxTool;
96 
97 	SubPackageNoPlatform[] subPackages;
98 
99 	BuildRequirement[] buildRequirements;
100 
101 	string[] versionFilters;
102 
103 	BuildOption[] buildOptions;
104 
105 	ToolchainRequirement[Toolchain] toolchainRequirements;
106 
107 	PackageDescriptionNoPlatform[] configurations;
108 
109 	string[string] subConfigurations;
110 
111 	bool opEquals(const(PackageDescriptionNoPlatform) other) const {
112 		import dud.pkgdescription.compare : areEqual;
113 		return areEqual(this, other);
114 	}
115 }
116 
117 struct SubPackageNoPlatform {
118 	UnprocessedPath path;
119 	Nullable!PackageDescriptionNoPlatform inlinePkg;
120 }
121 
122 struct DependencyNoPlatform {
123 @safe pure:
124 	string name;
125 	Nullable!VersionRange version_;
126 	UnprocessedPath path;
127 	Nullable!bool optional;
128 	Nullable!bool default_;
129 }
130 
131 PackageDescriptionNoPlatform selectImpl()(const(PackageDescription) pkg,
132 		const(Platform[]) platform)
133 {
134 	import dud.pkgdescription.helper : isMem;
135 	PackageDescriptionNoPlatform ret;
136 
137 	static foreach(mem; FieldNameTuple!PackageDescription) {{
138 		alias MemType = typeof(__traits(getMember, PackageDescription, mem));
139 		static if(canFind(
140 			[ isMem!"name", /*isMem!"version_",*/ isMem!"description"
141 			, isMem!"homepage", isMem!"authors", isMem!"copyright"
142 			, isMem!"license", isMem!"systemDependencies", isMem!"targetType"
143 			, isMem!"ddoxFilterArgs", isMem!"debugVersionFilters"
144 			, isMem!"versionFilters", isMem!"toolchainRequirements"
145 			, isMem!"workingDirectory", isMem!"mainSourceFile"
146 			, isMem!"targetPath", isMem!"targetName"
147 			], mem))
148 		{
149 			__traits(getMember, ret, mem) = ddup(__traits(getMember, pkg, mem));
150 		} else static if(canFind(
151 			[ isMem!"ddoxTool"
152 			, isMem!"preGenerateCommands"
153 			, isMem!"postGenerateCommands", isMem!"preBuildCommands"
154 			, isMem!"postBuildCommands", isMem!"preRunCommands"
155 			, isMem!"postRunCommands", isMem!"dflags", isMem!"lflags"
156 			, isMem!"libs", isMem!"versions"
157 			, isMem!"sourcePaths", isMem!"importPaths"
158 			, isMem!"copyFiles", isMem!"excludedSourceFiles"
159 			, isMem!"stringImportPaths", isMem!"sourceFiles"
160 			, isMem!"debugVersions", isMem!"subPackages"
161 			, isMem!"dependencies", isMem!"buildRequirements"
162 			, isMem!"subConfigurations", isMem!"buildOptions"
163 			], mem))
164 		{
165 			__traits(getMember, ret, mem) = select(
166 				__traits(getMember, pkg, mem), platform);
167 		} else static if(canFind(
168 			[ isMem!"configurations", isMem!"buildTypes"]
169 			, mem))
170 		{
171 			enforce(__traits(getMember, pkg, mem).empty,
172 				() @trusted {
173 					return format("%s %s", mem, __traits(getMember, pkg, mem));
174 				}());
175 		} else static if(canFind([ isMem!"platforms" ] , mem)) {
176 			// platforms are ignored
177 		} else {
178 			assert(false, format("Unhandeld '%s'", mem));
179 		}
180 	}}
181 
182 	return ret;
183 }
184 
185 //
186 // BuildOptions
187 //
188 
189 BuildOption[] select(const(BuildOptions) buildOptions,
190 		const(Platform[]) platform)
191 {
192 	auto keys = buildOptions.platforms.byKey()
193 		.filter!(key => isSuperSet(key, platform))
194 		.map!(key => ddup(key))
195 		.array
196 		.sort!((a, b) => a.length > b.length)();
197 
198 	BuildOption[] ret = keys.empty
199 		? BuildOption[].init
200 		: buildOptions.platforms[keys.front].ddup;
201 
202 	buildOptions.unspecifiedPlatform
203 		.each!((op) {
204 			if(!canFind(ret, op)) {
205 				ret ~= op;
206 			}
207 		});
208 
209 	return ret;
210 }
211 //
212 // SubConfigurations
213 //
214 
215 string[string] select(const(SubConfigs) subConfs,
216 		const(Platform[]) platform)
217 {
218 	auto keys = subConfs.configs.byKey()
219 		.filter!(key => isSuperSet(key, platform))
220 		.map!(key => ddup(key))
221 		.array
222 		.sort!((a, b) => a.length > b.length)();
223 
224 	string[string] ret = keys.empty
225 		? string[string].init
226 		: subConfs.configs[keys.front].ddup;
227 
228 	subConfs.unspecifiedPlatform.byKey()
229 		.each!((key) {
230 			if(key !in ret) {
231 				ret[key] = subConfs.unspecifiedPlatform[key].ddup;
232 			}
233 		});
234 
235 	return ret;
236 }
237 
238 //
239 // BuildRequirements
240 //
241 
242 BuildRequirement[] select(const(BuildRequirements) brs,
243 		const(Platform[]) platform)
244 {
245 	BuildRequirements brsC = ddup(brs);
246 	brsC.platforms.sort!((a, b) => a.platforms.length > b.platforms.length)();
247 	auto f = brsC.platforms.filter!(br => isSuperSet(br.platforms, platform));
248 	return f.empty ? BuildRequirement[].init : f.front.requirements;
249 }
250 
251 //
252 // Dependencies
253 //
254 
255 DependencyNoPlatform select()(const(Dependency) sp) {
256 	DependencyNoPlatform ret;
257 	ret.name = sp.name;
258 	ret.path = ddup(sp.path);
259 	if(!sp.version_.isNull()) {
260 		ret.version_ = ddup(sp.version_.get());
261 	}
262 	if(!sp.optional.isNull()) {
263 		ret.optional = sp.optional.get();
264 	}
265 	if(!sp.default_.isNull()) {
266 		ret.default_ = sp.default_.get();
267 	}
268 	return ret;
269 }
270 
271 DependencyNoPlatform[string] select()(const(Dependency[]) deps,
272 		const(Platform[]) platform)
273 {
274 	Dependency[][string] sorted;
275 	deps.filter!(dep => isSuperSet(dep.platforms, platform))
276 		.each!((dep) {
277 			auto d = ddup(dep);
278 			if(dep.name in sorted) {
279 				sorted[dep.name] ~= d;
280 			} else {
281 				sorted[dep.name] = [d];
282 			}
283 		});
284 
285 	DependencyNoPlatform[string] ret;
286 	foreach(key, ref values; sorted) {
287 		values.sort!((a, b) => a.platforms.length > b.platforms.length)();
288 		enforce(!values.empty, "values was unexceptionally empty");
289 		ret[key] = select(values.front);
290 	}
291 
292 	return ret;
293 }
294 
295 //
296 // SubPackage(s)
297 //
298 
299 SubPackageNoPlatform select()(const(SubPackage) sp, const(Platform[]) platform) {
300 	SubPackageNoPlatform ret;
301 	if(!sp.inlinePkg.isNull()) {
302 		ret.inlinePkg.opAssign(select(sp.inlinePkg.get(), platform));
303 	} else {
304 		ret.path = select(sp.path, platform);
305 	}
306 	return ret;
307 }
308 
309 SubPackageNoPlatform[] select()(const(SubPackage[]) sps, const(Platform[]) platform)
310 {
311 	return sps.map!(sp => select(sp, platform)).array;
312 
313 }
314 
315 //
316 // Path(s)
317 //
318 
319 UnprocessedPath select(const(Path) path, const(Platform[]) platform) {
320 	PathPlatform[] pth = path.platforms.map!(it => ddup(it)).array;
321 	auto superSets = selectLargestSuperset(pth, platform);
322 	return superSets.empty
323 		? UnprocessedPath.init
324 		: superSets.front.path;
325 }
326 
327 UnprocessedPath[] select(const(Paths) paths, const(Platform[]) platform) {
328 	PathsPlatform[] pths = paths.platforms.map!(it => ddup(it)).array;
329 	auto superSets = selectLargestSuperset(pths, platform);
330 	return superSets.empty
331 		? []
332 		: superSets.front.paths;
333 }
334 
335 //
336 // String(s)
337 //
338 
339 string select(const(String) str, const(Platform[]) platform) {
340 	StringPlatform[] strs = str.platforms.map!(it => ddup(it)).array;
341 	auto superSets = selectLargestSuperset(strs, platform);
342 	return superSets.empty
343 		? ""
344 		: superSets.front.str;
345 }
346 
347 string[] select(const(Strings) strs, const(Platform[]) platform) {
348 	StringsPlatform[] strss = strs.platforms.map!(it => ddup(it)).array;
349 	auto superSets = selectLargestSuperset(strss, platform);
350 	return superSets.empty
351 		? []
352 		: superSets.front.strs;
353 }
354 
355 //
356 // Helper
357 //
358 
359 auto selectLargestSuperset(T)(ref T ts,
360 		const(Platform[]) platform)
361 {
362 	ts.sort!((a, b) => a.platforms.length > b.platforms.length)();
363 	auto superSets = ts.filter!(it => isSuperSet(it.platforms, platform));
364 	return superSets;
365 }
366 
367 unittest {
368 	struct Test {
369 		int value;
370 		Platform[] platforms;
371 	}
372 
373 	{
374 		Test[] tests =
375 			[ Test(0, []), Test(1, [Platform.posix]), Test(2, [Platform.windows])
376 			, Test(3, [Platform.posix, Platform.x86_64, Platform.dmd])
377 			, Test(4, [Platform.posix, Platform.x86_64, Platform.gdc])
378 			];
379 
380 		auto a = selectLargestSuperset(tests, []);
381 		assert(!a.empty);
382 		assert(a.front.value == 0);
383 
384 		a = selectLargestSuperset(tests, [Platform.posix]);
385 		assert(!a.empty);
386 		assert(a.front.value == 1);
387 	}
388 	{
389 		Test[] tests =
390 			[ Test(1, [Platform.posix]), Test(2, [Platform.windows])
391 			, Test(3, [Platform.posix, Platform.x86_64, Platform.dmd])
392 			, Test(4, [Platform.posix, Platform.x86_64, Platform.gdc])
393 			];
394 
395 		auto a = selectLargestSuperset(tests, [Platform.osx, Platform.x86_64]);
396 		assert(a.empty, format("%s", a.front.value));
397 
398 		a = selectLargestSuperset(tests,
399 			[Platform.posix, Platform.x86_64, Platform.dmd]);
400 		assert(!a.empty);
401 		assert(a.front.value == 3);
402 
403 		a = selectLargestSuperset(tests,
404 			[Platform.posix, Platform.x86_64, Platform.gdc]);
405 		assert(!a.empty);
406 		assert(a.front.value == 4);
407 
408 		a = selectLargestSuperset(tests,
409 			[Platform.posix, Platform.x86_64, Platform.gdc, Platform.d_avx2]);
410 		assert(!a.empty);
411 		assert(a.front.value == 4);
412 	}
413 }
414 
415 bool isSuperSet(const(Platform[]) a, const(Platform[]) b) {
416 	return a.all!(p => canFind(b, p));
417 }
418 
419 unittest {
420 	auto a = [ Platform.posix, Platform.x86_64 ];
421 	auto b = [ Platform.posix, Platform.x86_64, Platform.d_avx2 ];
422 
423 	assert( isSuperSet(a, b));
424 	assert(!isSuperSet(b, a));
425 }
426