UNPKG

7.49 kBJavaScriptView Raw
1const pro = require("util").promisify;
2const fs = require("fs");
3const spawn = require("child_process").spawn;
4const codeGen = require("./code-gen.js");
5const rmDir = require("./rmdir.js");
6const Telnet = require("telnet-client");
7
8module.exports = async config => {
9
10 return {
11 async init(cli) {
12 return cli.command("build")
13 .option("-v, --verbose", "be verbose, display commands being run")
14 .option("-d, --dependencies [dependencies]", "run 'npm install' to update dependencies prior to the build")
15 .option("-a, --disassembly", "run 'objdump' to disassembly the image after build")
16 .option("-s, --size", "run 'size' to display image size after build")
17 .option("-f, --flash [port]", "flash the image using OpenOCD on given localhost port, defaults to 4444")
18 .option("-l, --loop", "stay in loop and repeat build after each source file modification");
19
20 },
21
22 async start(command) {
23
24 async function run(cmd, ...args) {
25 if (command.verbose) {
26 console.info(cmd, ...args);
27 }
28 return new Promise((resolve, reject) => {
29 let proc = spawn(cmd, args, {
30 stdio: "inherit"
31 });
32 proc.on("close", code => {
33 if (code === 0) {
34 resolve();
35 } else {
36 reject(`${cmd} returned code ${code}`);
37 }
38 });
39 });
40 }
41
42 let watched;
43
44 async function build() {
45
46 watched = [];
47 let buildDir = "build";
48
49 await rmDir(buildDir, true);
50 try {
51 await pro(fs.mkdir)(buildDir);
52 } catch (e) {
53 if (e.code !== "EEXIST") {
54 throw e;
55 }
56 }
57
58 if (command.dependencies) {
59 await run("npm", "install");
60 }
61
62 let packages = {};
63
64 async function scan(directory) {
65
66 if (command.verbose) {
67 console.info("Scanning", directory);
68 }
69
70 let package = JSON.parse(await pro(fs.readFile)(directory + "/package.json", "utf8"));
71 watched.push(directory + "/package.json");
72
73 if (packages[package.name]) {
74
75 if (package.version !== packages[package.name].version) {
76 throw `${package.name} version conflict ${package.version} ${packages[package.name].version}`;
77 }
78
79 } else {
80
81 package.directory = directory;
82
83 for (let dep in package.dependencies) {
84 await scan("./node_modules/" + dep);
85 }
86
87 packages[package.name] = package;
88 }
89
90 }
91
92 await scan(".");
93 let siliconPackages = Object.values(packages).filter(p => p.silicon);
94
95 let target;
96 siliconPackages.forEach(p => {
97 if (p.silicon && p.silicon.target) {
98 if (target) {
99 throw "Target is defined twice";
100 }
101 target = p.silicon.target;
102 }
103 });
104
105 let cpu = config.cpus[target.cpu];
106 if (!cpu) {
107 throw "Unsupported CPU: " + target.cpu;
108 }
109
110 function mapToArray(map, firstIndex = 0) {
111 return Object.entries(map).reduce((acc, [k, v]) => {
112 acc[parseInt(k) - firstIndex] = v;
113 return acc;
114 }, []);
115 }
116
117 let interrupts = mapToArray(cpu.interrupts || {}, 1).concat(
118 mapToArray(
119 siliconPackages.reduce((acc, p) => {
120 Object.entries(p.silicon.interrupts || {}).forEach(([k, v]) => acc[k] = v);
121 return acc;
122 }, {})
123 )
124 );
125
126
127 let siliconHpp = codeGen();
128
129 siliconHpp.wl("#ifndef SILICON_HPP");
130 siliconHpp.wl("#define SILICON_HPP");
131
132 siliconHpp.wl();
133 siliconHpp.wl("#include <stdlib.h>");
134
135 siliconHpp.wl();
136 siliconHpp.begin("namespace target {");
137 siliconHpp.begin("namespace interrupts {");
138 function writeInterrupts(kind, start, end) {
139 siliconHpp.begin(`namespace ${kind} {`);
140 for (let n = start; n < end; n++) {
141 let name = interrupts[n];
142 if (name) {
143 siliconHpp.wl(`const int ${name} = ${n - start};`);
144 }
145 }
146 siliconHpp.end("}");
147 }
148 writeInterrupts("Internal", 0, 15);
149 writeInterrupts("External", 15, interrupts.length);
150 writeInterrupts("All", 0, interrupts.length);
151 siliconHpp.end("}");
152 siliconHpp.end("}");
153 siliconHpp.wl();
154
155 function addIncludes(packages) {
156 packages.forEach(p => {
157 (p.silicon.sources || []).forEach(s => {
158 siliconHpp.wl(`#include "../${p.directory}/${s}"`);
159 watched.push(`${p.directory}/${s}`);
160 });
161 });
162 }
163
164 addIncludes(siliconPackages.filter(p => p.silicon.target));
165 addIncludes(siliconPackages.filter(p => !p.silicon.target));
166
167 siliconHpp.wl();
168 siliconHpp.wl("#endif // SILICON_HPP");
169
170 let siliconHppFile = "build/silicon.hpp";
171 await siliconHpp.toFile(siliconHppFile);
172
173 let siliconCpp = codeGen();
174 siliconCpp.wl("#include \"silicon.hpp\"");
175 let siliconCppFile = "build/silicon.cpp";
176 await siliconHpp.toFile(siliconCppFile);
177
178 let interruptsSFile = "build/interrupts.S";
179 let interruptsS = codeGen();
180 interruptsS.wl(`.section .text`);
181 interruptsS.wl(`.weak fatalError`);
182 interruptsS.wl(`fatalError:`);
183 interruptsS.wl(`b fatalError`);
184
185
186 interruptsS.wl(`.section .interrupts`);
187
188 for (let i = 0; i < interrupts.length; i++) {
189 let interrupt = interrupts[i];
190 let handler = "interruptHandler" + interrupt;
191 handler = "_Z" + handler.length + handler + "v";
192 if (interrupt) {
193 interruptsS.wl(`.weak ${handler}`);
194 interruptsS.wl(`.set ${handler}, fatalError`);
195 interruptsS.wl(`.word ${handler} + 1`);
196 } else {
197 interruptsS.wl(".word fatalError + 1");
198 }
199 }
200
201 interruptsS.toFile(interruptsSFile);
202
203 let imageFile = "build/build.elf";
204
205 let gccParams = [
206 "-I", "build",
207 "-T", cpu.ldScript,
208 "-nostartfiles",
209 "--specs=nano.specs",
210 "-fshort-wchar",
211 "-g",
212 "-Og",
213 "-std=c++14",
214 "-fno-rtti",
215 "-fno-exceptions",
216 "-ffunction-sections",
217 "-fdata-sections",
218 ...cpu.gccParams,
219 ...siliconPackages.reduce((acc, p) => {
220 return acc.concat(Object.entries(p.silicon.symbols || {}).map(([k, v]) => `-Wl,--defsym,${k}=${v}`));
221 }, []),
222 "-o", imageFile,
223 interruptsSFile,
224 cpu.startS,
225 siliconCppFile
226 ];
227
228 await run(cpu.gccPrefix + "gcc", ...gccParams);
229
230 if (command.disassembly) {
231 await run(cpu.gccPrefix + "objdump", "--section=.text", "-D", imageFile);
232 }
233
234 if (command.size) {
235 await run(cpu.gccPrefix + "size", imageFile);
236 }
237
238 if (command.flash) {
239
240 let connection = new Telnet();
241 let port = command.flash === true ? 4444 : parseInt(command.flash);
242
243 await connection.connect({
244 port,
245 shellPrompt: "> ",
246 debug: true
247 });
248
249 async function exec(cmd) {
250 let res = await connection.exec(cmd);
251 console.info(res);
252 return res;
253 }
254
255 await exec("halt");
256 await exec("reset halt");
257 await exec("flash write_image erase " + process.cwd() + "/" + imageFile);
258 await exec("reset run");
259
260 await connection.end();
261
262 }
263 }
264
265 async function watchedChanged() {
266 return new Promise((resolve, reject) => {
267 console.info("Waiting for a source file change...");
268 let watchers = [];
269 watched.forEach(file => {
270 watchers.push(fs.watch(file, () => {
271 watchers.forEach(w => w.close());
272 console.info(file, "changed");
273 resolve();
274 }));
275 });
276 });
277 }
278 ;
279
280 do {
281 try {
282 await build();
283 } catch (e) {
284 if (command.loop) {
285 console.error(e);
286 } else {
287 throw e;
288 }
289 }
290 if (!command.loop) {
291 break;
292 }
293 await watchedChanged();
294 } while (true);
295
296 }
297 };
298
299};
\No newline at end of file