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;
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 }
37 
38 enum buildInfoFileName = "build-info.json";
39 
40 void prepareResult()
41 {
42 	log("Moving...");
43 	if (resultDir.exists)
44 		resultDir.rmdirRecurse();
45 	rename(d.buildDir, resultDir);
46 
47 	log("Build successful.\n\nTo start using it, run `digger install`, or add %s to your PATH.".format(
48 		resultDir.buildPath("bin").absolutePath()
49 	));
50 }
51 
52 /// Build the customized D version.
53 /// The result will be in resultDir.
54 void runBuild(string spec, DManager.SubmoduleState submoduleState)
55 {
56 	d.build(submoduleState);
57 	prepareResult();
58 	std.file.write(buildPath(resultDir, buildInfoFileName), BuildInfo(diggerVersion, spec, d.config.build).toJson());
59 }
60 
61 /// Perform an incremental build, i.e. don't clean or fetch anything from remote repos
62 void incrementalBuild()
63 {
64 	d.rebuild();
65 	prepareResult();
66 }
67 
68 /// Run tests.
69 void runTests()
70 {
71 	d.test();
72 }
73 
74 /// Implements transient persistence for the current customization state.
75 struct DCustomizer
76 {
77 	struct CustomizationState
78 	{
79 		string spec;
80 		DManager.SubmoduleState submoduleState;
81 		string[string][string] pulls;
82 		string[string][string][string] forks;
83 	}
84 	CustomizationState state;
85 
86 	enum fileName = "customization-state.json";
87 
88 	void load()
89 	{
90 		state = fileName.readText().jsonParse!CustomizationState();
91 	}
92 
93 	void save()
94 	{
95 		std.file.write(fileName, state.toJson());
96 	}
97 
98 	void finish()
99 	{
100 		std.file.remove(fileName);
101 	}
102 
103 	string getPull(string submoduleName, string pullNumber)
104 	{
105 		string rev = state.pulls.get(submoduleName, null).get(pullNumber, null);
106 		if (!rev)
107 			state.pulls[submoduleName][pullNumber] = rev = d.getPull(submoduleName, pullNumber.to!int);
108 		return rev;
109 	}
110 
111 	string getFork(string submoduleName, string user, string branch)
112 	{
113 		string rev = state.forks.get(submoduleName, null).get(user, null).get(branch, null);
114 		if (!rev)
115 			state.forks[submoduleName][user][branch] = rev = d.getFork(submoduleName, user, branch);
116 		return rev;
117 	}
118 }
119 
120 DCustomizer customizer;
121 
122 int handleWebTask(string[] args)
123 {
124 	enforce(args.length, "No task specified");
125 	switch (args[0])
126 	{
127 		case "initialize":
128 			d.needUpdate();
129 			log("Ready.");
130 			return 0;
131 		case "begin":
132 			d.haveUpdate = true; // already updated in "initialize"
133 			customizer.state.spec = "digger-web @ " ~ (args.length == 1 ? "(master)" : args[1]);
134 			customizer.state.submoduleState = d.begin(parseRev(args.length == 1 ? null : args[1]));
135 			customizer.save();
136 			log("Ready.");
137 			return 0;
138 		case "merge":
139 			enforce(args.length == 3);
140 			customizer.load();
141 			d.merge(customizer.state.submoduleState, args[1], customizer.getPull(args[1], args[2]));
142 			customizer.save();
143 			return 0;
144 		case "unmerge":
145 			enforce(args.length == 3);
146 			customizer.load();
147 			d.unmerge(customizer.state.submoduleState, args[1], customizer.getPull(args[1], args[2]));
148 			customizer.save();
149 			return 0;
150 		case "merge-fork":
151 			enforce(args.length == 4);
152 			customizer.load();
153 			d.merge(customizer.state.submoduleState, args[2], customizer.getFork(args[2], args[1], args[3]));
154 			customizer.save();
155 			return 0;
156 		case "unmerge-fork":
157 			enforce(args.length == 4);
158 			customizer.load();
159 			d.unmerge(customizer.state.submoduleState, args[2], customizer.getFork(args[2], args[1], args[3]));
160 			customizer.save();
161 			return 0;
162 		case "callback":
163 			d.callback(args[1..$]);
164 			return 0;
165 		case "build":
166 		{
167 			customizer.load();
168 
169 			string model;
170 			getopt(args,
171 				"model", &model,
172 			);
173 			enforce(args.length == 1, "Unrecognized build option");
174 
175 			if (model.length)
176 				d.config.build.components.common.models = [model];
177 
178 			runBuild(customizer.state.spec, customizer.state.submoduleState);
179 			customizer.finish();
180 			return 0;
181 		}
182 		case "branches":
183 			d.getMetaRepo().needRepo();
184 			foreach (line; d.getMetaRepo().git.query("branch", "--remotes").splitLines())
185 				if (line.startsWith("  origin/") && line[2..$].indexOf(" ") < 0)
186 					writeln(line[9..$]);
187 			return 0;
188 		case "tags":
189 			d.getMetaRepo().needRepo();
190 			d.getMetaRepo().git.run("tag");
191 			return 0;
192 		case "install-preview":
193 			install.install(false, true);
194 			return 0;
195 		case "install":
196 			install.install(true, false);
197 			return 0;
198 		default:
199 			assert(false);
200 	}
201 }
202 
203 DManager.SubmoduleState parseSpec(string spec)
204 {
205 	auto parts = spec.split("+");
206 	parts = parts.map!strip().array();
207 	if (parts.empty)
208 		parts = [null];
209 	auto rev = parseRev(parts.shift());
210 
211 	auto state = d.begin(rev);
212 
213 	foreach (part; parts)
214 	{
215 		bool revert = part.skipOver("-");
216 
217 		void handleCommit(string component, string commit, int mainline)
218 		{
219 			if (revert)
220 				d.revert(state, component, commit, mainline);
221 			else
222 				d.merge(state, component, commit);
223 		}
224 
225 		void handleBranch(string component, string branch)
226 		{
227 			if (revert)
228 			{
229 				string commit; int mainline;
230 				d.getChild(state, component, branch, /*out*/commit, /*out*/mainline);
231 				handleCommit(component, commit, mainline);
232 			}
233 			else
234 				handleCommit(component, branch, 0);
235 		}
236 
237 		if (part.matchCaptures(re!`^(\w[\w\-\.]*)#(\d+)$`,
238 			(string component, int pull)
239 			{
240 				handleBranch(component, d.getPull(component, pull));
241 			}))
242 			continue;
243 
244 		if (part.matchCaptures(re!`^(\w+)/(\w[\w\-\.]*)/(\w[\w\-]*)$`,
245 			(string user, string component, string branch)
246 			{
247 				handleBranch(component, d.getFork(component, user, branch));
248 			}))
249 			continue;
250 
251 		if (part.matchCaptures(re!`^(\w+)/([0-9a-fA-F]{40})$`,
252 			(string component, string commit)
253 			{
254 				handleCommit(component, commit, 0);
255 			}))
256 			continue;
257 
258 		throw new Exception("Don't know how to apply customization: " ~ spec);
259 	}
260 
261 	return state;
262 }
263 
264 /// Build D according to the given spec string
265 /// (e.g. master+dmd#123).
266 void buildCustom(string spec)
267 {
268 	log("Building spec: " ~ spec);
269 	auto submoduleState = parseSpec(spec);
270 	runBuild(spec, submoduleState);
271 }
272 
273 void checkout(string spec)
274 {
275 	log("Checking out: " ~ spec);
276 	auto submoduleState = parseSpec(spec);
277 	d.checkout(submoduleState);
278 	log("Done.");
279 }
280 
281 /// Build all D versions (for the purpose of caching them).
282 /// Build order is in steps of decreasing powers of two.
283 void buildAll(string spec)
284 {
285 	d.needUpdate();
286 	auto commits = d.getLog("refs/remotes/origin/" ~ spec);
287 	commits.reverse(); // oldest first
288 
289 	for (int step = 1 << 30; step; step >>= 1)
290 	{
291 		if (step >= commits.length)
292 			continue;
293 
294 		log("Building all revisions with step %d (%d/%d revisions)".format(step, commits.length/step, commits.length));
295 
296 		for (int n = step; n < commits.length; n += step)
297 			try
298 			{
299 				auto state = d.begin(commits[n].hash);
300 				if (!d.isCached(state))
301 				{
302 					log("Building revision %d/%d".format(n/step, commits.length/step));
303 					d.build(state);
304 				}
305 			}
306 			catch (Exception e)
307 				log(e.toString());
308 	}
309 }