Skip to content

Commit 8d979d0

Browse files
committed
improve build
1 parent ae868a9 commit 8d979d0

File tree

6 files changed

+153
-37
lines changed

6 files changed

+153
-37
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ out
1616
.DS*
1717
.build.log
1818
node_modules
19-
*.tar.gz
19+
*.tar.gz
20+
.atomtools-cache.json

build.config.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export default {
2626
mformat: {
2727
testCommand() { return `${this.path} --version`; }
2828
},
29+
mmd: {
30+
testCommand() { return `${this.path} --version`; }
31+
},
2932
mcopy: {
3033
testCommand() { return `${this.path} --version`; }
3134
},
@@ -92,13 +95,18 @@ export default {
9295
name: "fat32",
9396
output: "$BUILD/fat32.img",
9497
require: {
95-
tools: {"mformat": "MFORMAT", "mcopy": "MCOPY", "dd": "DD"}
98+
tools: {"mformat": "MFORMAT", "mcopy": "MCOPY", "dd": "DD", "mmd": "MMD"}
9699
},
97100
build(files) {
98101
let steps = [];
99102
steps.push(`$DD bs=67M count=1 if=/dev/zero of=$BUILD/fat32.img`);
100103
steps.push(`$MFORMAT -F -i $BUILD/fat32.img ::`);
101104
for (let [location, copyTo] of files) {
105+
let dirs = copyTo.split("/").slice(0, -1);
106+
for (let i = 0; i < dirs.length; i++) {
107+
let dirPath = dirs.slice(0, i + 1).join("/");
108+
steps.push(`$MMD -i $BUILD/fat32.img ::/${dirPath}`);
109+
}
102110
steps.push(`$MCOPY -i $BUILD/fat32.img ${location} ::/${copyTo}`);
103111
}
104112
return steps;

build.mts

Lines changed: 134 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ console.misc = function (...args: string[]) {
6767
}
6868

6969
let projectConfiguration: any;
70-
let buildFiles: { [path: string]: any } = [];
70+
let buildFiles: { [path: string]: any } = {};
71+
const CONFIG_CACHE_PATH = "./.atomtools-cache.json";
72+
const CACHE_VERSION = 1;
7173

7274
function isIterable(obj: any): boolean {
7375
// checks for null and undefined
@@ -111,6 +113,10 @@ async function populateToolPaths() {
111113
let success = true;
112114
let promises = []
113115
for (let tool of Object.keys(tools) as (string)[]) {
116+
if (tools[tool].path) {
117+
console.debug(` -> Using cached ${tool} at ${tools[tool].path}`);
118+
continue;
119+
}
114120
promises.push(
115121
which(tool).catch(() => null).then(item => {
116122
if (!item) {
@@ -147,6 +153,10 @@ async function populateDirectoryPaths() {
147153
let promises = [];
148154
for (let dir of Object.keys(directories) as (keyof typeof directories)[]) {
149155
let item = directories[dir];
156+
if (item.path) {
157+
console.debug(` -> Using cached ${item.name} path at ${item.path}`);
158+
continue;
159+
}
150160
promises.push(runCommand("sh", ["-c", item.find[0] as string]).catch(() => ({ code: -1, stdout: "", stderr: "" })).then(({
151161
stdout, stderr, code
152162
}) => {
@@ -182,31 +192,61 @@ async function populateDirectoryPaths() {
182192
let primaryTarget: string;
183193

184194
async function getBuildFiles() {
185-
// walk every subdirectory in project
186-
for (let item of projectConfiguration.source) {
187-
let folders = fs.readdirSync(item)
188-
for (let folder of folders) {
189-
let fullPath = `${item}/${folder}`;
190-
let stat = await promisify(fs.stat)(fullPath);
191-
if (stat.isDirectory()) {
192-
folders.push(...fs.readdirSync(fullPath).map(subitem => `${folder}/${subitem}`));
195+
buildFiles = {} as any;
196+
const importPromises: Promise<void>[] = [];
197+
198+
for (let sourceRoot of projectConfiguration.source) {
199+
const queue: string[] = [sourceRoot];
200+
while (queue.length) {
201+
const current = queue.pop() as string;
202+
let entries: string[] = [];
203+
try {
204+
entries = fs.readdirSync(current);
205+
} catch {
206+
continue;
193207
}
194-
if (stat.isFile() && (folder.endsWith("/build.js") || folder.endsWith("/build.json"))) {
195-
const file = await import(fullPath);
196-
if (!file.default.atomtools) continue;
197-
console.debug(` -> Found build file: ${fullPath}`);
198-
buildFiles[fullPath] = file.default;
208+
for (let entry of entries) {
209+
const fullPath = `${current}/${entry}`;
210+
let stat;
211+
try {
212+
stat = fs.statSync(fullPath);
213+
} catch {
214+
continue;
215+
}
216+
if (stat.isDirectory()) {
217+
queue.push(fullPath);
218+
continue;
219+
}
220+
if (entry === "build.js" || entry === "build.json") {
221+
importPromises.push(
222+
import(fullPath).then(file => {
223+
if (!file.default?.atomtools) return;
224+
console.debug(` -> Found build file: ${fullPath}`);
225+
buildFiles[fullPath] = file.default;
226+
}).catch((err) => {
227+
console.warn(` Warning: skipping build file ${fullPath}: ${err}`);
228+
})
229+
);
230+
}
199231
}
200232
}
201233
}
234+
235+
await Promise.all(importPromises);
202236
console.info(`-> Found ${Object.keys(buildFiles).length} build files`);
203237
}
204238

205239
async function configure() {
206-
console.info("-> Configuring tools...");
207-
await populateToolPaths();
208-
console.info("-> Getting required paths...");
209-
await populateDirectoryPaths();
240+
const cacheLoaded = await loadConfigurationCache();
241+
if (!cacheLoaded) {
242+
console.info("-> Configuring tools...");
243+
await populateToolPaths();
244+
console.info("-> Getting required paths...");
245+
await populateDirectoryPaths();
246+
await writeConfigurationCache();
247+
} else {
248+
console.info("-> Using cached tool/path resolution...");
249+
}
210250
console.info("-> Enumerating build files...");
211251
await getBuildFiles();
212252
}
@@ -221,6 +261,65 @@ async function getCreationDateOfPath(path: string): Promise<number> {
221261
}
222262
}
223263

264+
async function isOutputStale(inputPath: string, outputPath: string): Promise<boolean> {
265+
// Skip work when the produced output is already newer than its input
266+
const [inputTime, outputTime] = await Promise.all([
267+
getCreationDateOfPath(inputPath),
268+
getCreationDateOfPath(outputPath)
269+
]);
270+
return outputTime < inputTime;
271+
}
272+
273+
async function loadConfigurationCache(): Promise<boolean> {
274+
if (!fs.existsSync(CONFIG_CACHE_PATH)) return false;
275+
try {
276+
const cache = JSON.parse(fs.readFileSync(CONFIG_CACHE_PATH, "utf-8"));
277+
if (cache.version !== CACHE_VERSION) return false;
278+
279+
const configStamp = await getCreationDateOfPath("./build.config.js");
280+
if (cache.configStamp !== configStamp) return false;
281+
282+
let toolsComplete = true;
283+
for (let tool of Object.keys(projectConfiguration.tools || {})) {
284+
const cachedPath = cache.tools?.[tool];
285+
if (!cachedPath) {
286+
toolsComplete = false;
287+
continue;
288+
}
289+
projectConfiguration.tools[tool].path = cachedPath;
290+
}
291+
292+
let pathsComplete = true;
293+
for (let pathKey of Object.keys(projectConfiguration.paths || {})) {
294+
const cachedPath = cache.paths?.[pathKey];
295+
if (!cachedPath) {
296+
pathsComplete = false;
297+
continue;
298+
}
299+
projectConfiguration.paths[pathKey].path = cachedPath;
300+
}
301+
302+
return toolsComplete && pathsComplete;
303+
} catch {
304+
return false;
305+
}
306+
}
307+
308+
async function writeConfigurationCache() {
309+
const configStamp = await getCreationDateOfPath("./build.config.js");
310+
const cache = {
311+
version: CACHE_VERSION,
312+
configStamp,
313+
tools: Object.fromEntries(Object.entries(projectConfiguration.tools || {}).map(([key, value]: [string, any]) => [key, value.path])),
314+
paths: Object.fromEntries(Object.entries(projectConfiguration.paths || {}).map(([key, value]: [string, any]) => [key, value.path]))
315+
};
316+
try {
317+
fs.writeFileSync(CONFIG_CACHE_PATH, JSON.stringify(cache, null, 2));
318+
} catch (err: any) {
319+
console.warn(` Warning: Unable to write configuration cache: ${err}`);
320+
}
321+
}
322+
224323
async function buildTarget() {
225324
console.log(`-> Starting build for target "${primaryTarget}"...`);
226325
// Step 0: create the build directory
@@ -235,11 +334,16 @@ async function buildTarget() {
235334
exit(1);
236335
}
237336

337+
// reset per-run mutation markers
338+
target.wasModified = false;
339+
const targetOutputPath = escapeCommand(target.output, target.require || {}, "./");
340+
238341
// Step 1.5: see if the target is newer than its dependencies
239342
// if so, we can skip the build entirely
240-
let targetTime = await getCreationDateOfPath(escapeCommand(target.output, target.require || {}, "./"));
343+
let targetTime = await getCreationDateOfPath(targetOutputPath);
241344
// Step 2: let's find dependencies first and foremost.
242345
// these deps need to be built first.
346+
let depsModified = false;
243347
for (let dep of (target.depends || [])) {
244348
console.info(`-> Building dependency "${dep}"...`);
245349
// let's look to see if this is a target or location
@@ -251,8 +355,10 @@ async function buildTarget() {
251355
primaryTarget = dep;
252356
await buildTarget();
253357
primaryTarget = currentTarget;
358+
depsModified = depsModified || !!projectConfiguration.targets.find((t: any) => t.name === dep)?.wasModified;
254359
continue;
255360
}
361+
if (depLocation) depLocation.wasModified = false;
256362
let collectedFiles = []
257363
for (let [dir, buildFile] of Object.entries(buildFiles)) {
258364
dir = dir.split("/").slice(0, -1).join("/")
@@ -301,6 +407,11 @@ async function buildTarget() {
301407
if (!fs.existsSync(outputFile.split("/").slice(0, -1).join("/"))) {
302408
fs.mkdirSync(outputFile.split("/").slice(0, -1).join("/"), { recursive: true });
303409
}
410+
411+
if (!(await isOutputStale(inputFile, outputFile))) {
412+
console.misc(` -> Skipping ${inputFile} (cached)`);
413+
continue;
414+
}
304415
for (let subcommand of command.build) {
305416
if (subcommand.type === "command") {
306417
let finalCommand = subcommand.command.replaceAll("$INPUT", inputFile).replaceAll("$OUTPUT", outputFile);
@@ -354,6 +465,7 @@ async function buildTarget() {
354465
}
355466
if (projectConfiguration.locations.find(({name}: any) => name === depLocation.name).wasModified) {
356467
projectConfiguration.targets.find(({name}: any) => name === primaryTarget).wasModified = true;
468+
depsModified = true;
357469

358470
let nextCommands = depLocation.build(collectedFiles);
359471
for (let command of nextCommands) {
@@ -383,8 +495,8 @@ async function buildTarget() {
383495
// Step 3: We built all the dependencies to the target
384496
// so now we build the actual target
385497
// very simple since targets are assumed to be commands only with no dynamics
386-
if (!projectConfiguration.targets.find(({name}: any) => name === primaryTarget).wasModified) {
387-
console.misc(` -> Using cached build for target "${primaryTarget}"`);
498+
if (!depsModified && fs.existsSync(targetOutputPath)) {
499+
console.misc(` -> Using cached build for target "${primaryTarget}" (deps unchanged)`);
388500
return;
389501
}
390502
let targetPrerequisites: any = {};
@@ -399,6 +511,7 @@ async function buildTarget() {
399511
targetPrerequisites[prereqVar] = prereqResult.stdout.trim();
400512
}
401513
}
514+
target.wasModified = true;
402515
for (let command of target.build) {
403516
let commandStr = escapeCommand(command, target.require, "./");
404517
for (let [prereqVar, prereqValue] of Object.entries(targetPrerequisites) as [string, string][]) {

src/primary/drivers/display/vgabiostext/build.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default {
1616
output: ["textmode.adv"],
1717
install: {
1818
fat32: [[
19-
"$BUILD/textmode.adv", "textmode.adv"
19+
"$BUILD/textmode.adv", "drivers/textmode.adv"
2020
]]
2121
}
2222
}

src/primary/kernel/kernel/kernel.c

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
extern void ahsh();
3333

3434
static Fat fat;
35-
3635
bool write_stub(const u8* buf, unsigned int sect) {
3736
u8 err = ide_write_sectors(0, 1, sect, (void*)buf);
3837
return err == 0;
@@ -59,13 +58,11 @@ u8 mount() {
5958
return 0;
6059
}
6160

62-
6361
// only blocking thread.
6462
int main() {
6563
clear_screen();
6664
println_color("AstatineOS v0.3.0-alpha", COLOR_LIGHT_RED);
6765

68-
6966
gdt_init();
7067
printf("Target complete: gdt\n");
7168

@@ -80,7 +77,6 @@ int main() {
8077
fpu_init();
8178
printf("Target complete: fpu\n");
8279

83-
8480
pcs_init();
8581
printf("Target complete: pcspeaker\n");
8682

@@ -90,7 +86,6 @@ int main() {
9086
keyboard_init();
9187
printf("Target complete: keyboard\n");
9288

93-
9489
// clear the 0x100000-0x1FFFFF region to prevent dynamic memory corruption
9590
printf("Setting up dymem region...");
9691
for (u32* ptr = (u32*)0x100000; ptr < (u32*)0x400000; ptr++) {
@@ -128,7 +123,7 @@ int main() {
128123

129124
clear_screen();
130125
printf("Ready to load driver, enter path: ");
131-
char elf_path[127] = "/primary/textmode.adv";
126+
char elf_path[127] = "/primary/drivers/textmode.adv";
132127
if (is_elf(elf_path) == 0) {
133128
printf("ELF file detected, loading...\n");
134129
File file;
@@ -138,12 +133,8 @@ int main() {
138133
for (u32 i = 0; x[i] != 0x00; i++) {
139134
*((u8*)0xb8000 + i * 2) = x[i];
140135
}
141-
reboot();
136+
goto skiploading;
142137
}
143-
// char* x = "loaded";
144-
// for (u32 i = 0; x[i] != 0x00; i++) {
145-
// *((u8*)0xb8000 + i * 2) = x[i];
146-
// }
147138
int errno;
148139
if ((errno = attempt_install_driver(&file)) != 0) {
149140
printf("Failed to load and run ELF file.\n");
@@ -152,16 +143,18 @@ int main() {
152143
for (u32 i = 0; x[i] != 0x00; i++) {
153144
*((u8*)0xb8000 + i * 2) = x[i];
154145
}
155-
reboot();
146+
goto skiploading;
156147
}
157148
} else {
158149
printf("The specified file is not a valid ELF file.\n");
159150
char* x = "not an elf.";
160151
for (u32 i = 0; x[i] != 0x00; i++) {
161152
*((u8*)0xb8000 + i * 2) = x[i];
162153
}
163-
reboot();
154+
goto skiploading;
164155
}
156+
skiploading:
157+
165158
while(1) {
166159
clear_screen();
167160
printf("Ready to load elf, enter path: ");

src/primary/static/build.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export default {
22
atomtools: true,
3+
output: ["generalfile", "musictrack"],
34
install: {
45
fat32: [[
56
"$DIR/generalfile", "generalfile"

0 commit comments

Comments
 (0)