1 module custom;
2 
3 import std.algorithm;
4 import std.array;
5 import std.conv;
6 import std.exception;
7 import std.file;
8 import std.getopt;
9 import std.path;
10 import std.stdio;
11 import std.string;
12 
13 import ae.sys.d.manager;
14 import ae.utils.array;
15 import ae.utils.json;
16 import ae.utils.regex;
17 
18 import common;
19 import config : config, subDir;
20 import install;
21 import repo;
22 
23 alias indexOf = std..string.indexOf;
24 
25 // https://issues.dlang.org/show_bug.cgi?id=15777
26 alias strip = std..string.strip;
27 
28 alias subDir!"result" resultDir;
29 
30 /// We save a JSON file to the result directory with the build parameters.
31 struct BuildInfo
32 {
33 	string diggerVersion;
34 	string spec;
35 	DiggerManager.Config.Build config;
36 	DManager.SubmoduleState components;
37 }
38 
39 enum buildInfoFileName = "build-info.json";
40 
41 void prepareResult()
42 {
43 	log("Moving...");
44 	if (resultDir.exists)
45 		resultDir.rmdirRecurse();
46 	rename(d.buildDir, resultDir);
47 
48 	log("Build successful.\n\nTo start using it, run `digger install`, or add %s to your PATH.".format(
49 		resultDir.buildPath("bin").absolutePath()
50 	));
51 }
52 
53 /// Build the customized D version.
54 /// The result will be in resultDir.
55 void runBuild(string spec, DManager.SubmoduleState submoduleState, bool asNeeded)
56 {
57 	auto buildInfoPath = buildPath(resultDir, buildInfoFileName);
58 	auto buildInfo = BuildInfo(diggerVersion, spec, d.config.build, submoduleState);
59 	if (asNeeded && buildInfoPath.exists && buildInfoPath.readText.jsonParse!BuildInfo == buildInfo)
60 	{
61 		log("Reusing existing version in " ~ resultDir);
62 		return;
63 	}
64 	d.build(submoduleState);
65 	prepareResult();
66 	std.file.write(buildInfoPath, buildInfo.toJson());
67 }
68 
69 /// Perform an incremental build, i.e. don't clean or fetch anything from remote repos
70 void incrementalBuild()
71 {
72 	d.rebuild();
73 	prepareResult();
74 }
75 
76 /// Run tests.
77 void runTests()
78 {
79 	d.test();
80 }
81 
82 DManager.SubmoduleState parseSpec(string spec)
83 {
84 	auto parts = spec.split("+");
85 	parts = parts.map!strip().array();
86 	if (parts.empty)
87 		parts = [null];
88 	auto rev = parseRev(parts.shift());
89 
90 	auto state = d.begin(rev);
91 
92 	foreach (part; parts)
93 	{
94 		bool revert = part.skipOver("-");
95 
96 		void apply(string component, string[2] branch, DManager.MergeMode mode)
97 		{
98 			if (revert)
99 				d.revert(state, component, branch, mode);
100 			else
101 				d.merge(state, component, branch, mode);
102 		}
103 
104 		if (part.matchCaptures(re!`^(\w[\w\-\.]*)#(\d+)$`,
105 			(string component, int pull)
106 			{
107 				apply(component, d.getPull(component, pull), DManager.MergeMode.cherryPick);
108 			}))
109 			continue;
110 
111 		if (part.matchCaptures(re!`^(?:([a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])/)?(\w[\w\-\.]*)/(?:(\w[\w\-]*)\.\.)?(\w[\w\-]*)$`,
112 			(string user, string component, string base, string tip)
113 			{
114 				// Some "do what I mean" logic here: if the user
115 				// specified a range, or a single commit, cherry-pick;
116 				// otherwise (just a branch name), do a git merge
117 				auto branch = d.getBranch(component, user, base, tip);
118 				auto mode = branch[0] ? DManager.MergeMode.cherryPick : DManager.MergeMode.merge;
119 				apply(component, branch, mode);
120 			}))
121 			continue;
122 
123 		throw new Exception("Don't know how to apply customization: " ~ spec);
124 	}
125 
126 	return state;
127 }
128 
129 /// Build D according to the given spec string
130 /// (e.g. master+dmd#123).
131 void buildCustom(string spec, bool asNeeded = false)
132 {
133 	log("Building spec: " ~ spec);
134 	auto submoduleState = parseSpec(spec);
135 	runBuild(spec, submoduleState, asNeeded);
136 }
137 
138 void checkout(string spec)
139 {
140 	log("Checking out: " ~ spec);
141 	auto submoduleState = parseSpec(spec);
142 	d.checkout(submoduleState);
143 	log("Done.");
144 }
145 
146 /// Build all D versions (for the purpose of caching them).
147 /// Build order is in steps of decreasing powers of two.
148 void buildAll(string spec)
149 {
150 	d.needUpdate();
151 	auto commits = d.getLog("refs/remotes/origin/" ~ spec);
152 	commits.reverse(); // oldest first
153 
154 	for (int step = 1 << 30; step; step >>= 1)
155 	{
156 		if (step >= commits.length)
157 			continue;
158 
159 		log("Building all revisions with step %d (%d/%d revisions)".format(step, commits.length/step, commits.length));
160 
161 		for (int n = step; n < commits.length; n += step)
162 			try
163 			{
164 				auto state = d.begin(commits[n].hash);
165 				if (!d.isCached(state))
166 				{
167 					log("Building revision %d/%d".format(n/step, commits.length/step));
168 					d.build(state);
169 				}
170 			}
171 			catch (Exception e)
172 				log(e.toString());
173 	}
174 }