DEADSOFTWARE

cpmake: execute external scripts that execute compiler and linker
[cpc.git] / src / generic / Dsw / Mod / MakeMain.cp
1 MODULE DswMakeMain;
3 IMPORT Kernel, Files, Log, Strings, DswOpts, DswProcs, DswDocuments, DevCPM, DevCPT, DevCPR, DevCPS;
5 CONST
6 version = "0.3";
8 modDir = "Mod";
9 sysDir = "System";
11 maxImps = 127;
12 maxJobs = maxImps;
14 (* symbol values *)
15 null = 0; times = 1; slash = 2; div = 3; mod = 4;
16 and = 5; plus = 6; minus = 7; or = 8; eql = 9;
17 neq = 10; lss = 11; leq = 12; gtr = 13; geq = 14;
18 in = 15; is = 16; arrow = 17; dollar = 18; period = 19;
19 comma = 20; colon = 21; upto = 22; rparen = 23; rbrak = 24;
20 rbrace = 25; of = 26; then = 27; do = 28; to = 29;
21 by = 30; not = 33;
22 lparen = 40; lbrak = 41; lbrace = 42; becomes = 44;
23 number = 45; nil = 46; string = 47; ident = 48; semicolon = 49;
24 bar = 50; end = 51; else = 52; elsif = 53; until = 54;
25 if = 55; case = 56; while = 57; repeat = 58; for = 59;
26 loop = 60; with = 61; exit = 62; return = 63; array = 64;
27 record = 65; pointer = 66; begin = 67; const = 68; type = 69;
28 var = 70; out = 71; procedure = 72; close = 73; import = 74;
29 module = 75; eof = 76;
31 (* module state flags *)
32 imported = 0; trace = 1; hasObj = 2; hasSym = 3; hasErrors = 4; library = 5;
34 debugImport = FALSE;
35 debugOrder = FALSE;
36 debugJobs = FALSE;
38 TYPE
39 String = POINTER TO ARRAY OF CHAR;
41 Selector = POINTER TO RECORD
42 name: DevCPT.Name;
43 value: BOOLEAN;
44 next: Selector
45 END;
47 Module = POINTER TO RECORD
48 name: DevCPT.Name;
49 odc: Files.Name;
50 depth: INTEGER; (* 0: leaf, MAX: root *)
51 dir: DevCPM.Directory;
52 mno: INTEGER;
53 imp: ARRAY maxImps OF Module;
54 flags: SET;
55 worker: DswProcs.Process;
56 END;
58 VAR (* options *)
59 auto, trap, clean: BOOLEAN;
60 exe, target, base: String;
61 jobs: INTEGER;
63 VAR
64 werr, err: INTEGER;
65 mno, rno: INTEGER; (* num modules *)
66 modList, lnkList, cmpList: ARRAY maxImps OF Module;
67 def: Selector; (* with head, global list of selectors *)
68 dirList: DevCPM.Directory;
70 PROCEDURE Error (IN str, p0, p1: ARRAY OF CHAR; i2: INTEGER);
71 VAR p2: ARRAY 32 OF CHAR;
72 BEGIN
73 Strings.IntToString(i2, p2);
74 Log.String("cpmake: "); Log.ParamMsg(str, p0, p1, p2); Log.Ln;
75 INC(err);
76 IF trap THEN
77 HALT(100)
78 END
79 END Error;
81 (* --------- options --------- *)
83 PROCEDURE IdentLen (IN s: ARRAY OF CHAR): INTEGER;
84 VAR i: INTEGER;
85 BEGIN
86 i := 0;
87 IF Strings.IsIdentStart(s[0]) THEN
88 REPEAT INC(i) UNTIL ~Strings.IsIdent(s[i])
89 END;
90 RETURN i
91 END IdentLen;
93 PROCEDURE Define (n: ARRAY OF CHAR; overwrite: BOOLEAN);
94 VAR i: INTEGER; v: BOOLEAN; s: Selector;
95 BEGIN
96 i := IdentLen(n);
97 IF i # 0 THEN
98 IF ~((n[i] = 0X) OR (((n[i] = "+") OR (n[i] = "-")) & (n[i + 1] = 0X))) THEN
99 Error("defines: expectd + or - after identifier (^0)", n, "", 0)
100 END;
101 v := n[i] # "-"; n[i] := 0X; s := def;
102 WHILE (s.next # NIL) & (s.next.name$ # n$) DO s := s.next END;
103 IF s.next = NIL THEN
104 NEW(s.next);
105 Strings.StringToUtf8(n, s.next.name, i);
106 ASSERT(i = 0);
107 s.next.value := v
108 ELSIF overwrite THEN
109 s.next.value := v
110 END
111 ELSE Error("defines: '^0' not valid identifier", n, "", 0)
112 END
113 END Define;
115 PROCEDURE AddModule (IN n: ARRAY OF CHAR; selectors: Selector; dir: DevCPM.Directory);
116 VAR i, res: INTEGER; m: Module;
117 BEGIN
118 i := IdentLen(n);
119 IF (i # 0) & (n[i] = 0X) THEN
120 i := 0;
121 WHILE (i < mno) & (modList[i].name$ # n$) DO INC(i) END;
122 IF i >= mno THEN
123 NEW(m);
124 Strings.StringToUtf8(n, m.name, res);
125 ASSERT(res = 0);
126 m.dir := dir;
127 modList[i] := m;
128 INC(mno)
129 END
130 ELSE
131 Error("invalid module name", "", "", 0)
132 END
133 END AddModule;
135 PROCEDURE StrToInt (IN s: ARRAY OF CHAR; def: INTEGER): INTEGER;
136 VAR x, res: INTEGER;
137 BEGIN
138 Strings.StringToInt(s, x, res);
139 IF res # 0 THEN
140 Error("invalid integer value", s, "", 0);
141 x := def
142 END;
143 RETURN x
144 END StrToInt;
146 PROCEDURE NewStr (IN s: ARRAY OF CHAR): String;
147 VAR p: String;
148 BEGIN
149 NEW(p, LEN(s$) + 1); p^ := s$;
150 RETURN p
151 END NewStr;
153 PROCEDURE Help;
154 BEGIN
155 Log.String("Usage: cpmake [options] module..."); Log.Ln;
156 Log.String("Options:"); Log.Ln;
157 Log.String(" -a Enable automatic dependency resolution"); Log.Ln;
158 Log.String(" -c Remove all generated files"); Log.Ln;
159 Log.String(" -t target Specify target rules"); Log.Ln;
160 Log.String(" -o file Generate object file"); Log.Ln;
161 Log.String(" -j num Specifies the number of jobs to run simultaneously"); Log.Ln;
162 Log.String(" -f path Specifies path to directory with targets"); Log.Ln;
163 Log.String(" -h Print help"); Log.Ln;
164 Log.String(" -v Print version"); Log.Ln;
165 Kernel.Quit(0)
166 END Help;
168 PROCEDURE ParseArgs;
169 BEGIN
170 exe := NIL; auto := FALSE; jobs := 1; def.next := NIL; mno := 0; rno := 0;
171 target := NewStr("default"); base := NewStr("cprules");
172 LOOP
173 CASE DswOpts.GetOpt("acdgo:t:j:f:") OF
174 | "a": auto := TRUE
175 | "c": clean := TRUE
176 | "d": trap := TRUE
177 | "f": base := DswOpts.str
178 | "h": Help
179 | "j": jobs := MIN(MAX(StrToInt(DswOpts.str, 1), 1), maxJobs)
180 | "o": exe := DswOpts.str
181 | "t": target := DswOpts.str
182 | "v": Log.String(version); Log.Ln; Kernel.Quit(0)
183 | ":": Error("missing argument for option -^0", DswOpts.str, "", 0)
184 | "?": Error("unknown option -^0", DswOpts.str, "", 0)
185 | "$": AddModule(DswOpts.str, def, dirList)
186 | 00X: EXIT
187 END
188 END
189 END ParseArgs;
191 PROCEDURE ReadDefines;
192 VAR loc: Files.Locator; name: Files.Name; m: DswDocuments.Model; r: DswDocuments.Reader; i, res: INTEGER;
193 BEGIN
194 loc := Files.dir.This(base).This(target);
195 DswDocuments.Open(loc, "defines", m, res);
196 IF res = 0 THEN
197 r := m.NewReader(NIL);
198 r.SetPos(0);
199 r.Read;
200 WHILE ~r.eot DO
201 i := 0;
202 WHILE ~r.eot & (r.char <= 20X) DO r.Read END;
203 WHILE ~r.eot & (r.char > 20X) DO name[i] := r.char; r.Read; INC(i) END;
204 IF i # 0 THEN name[i] := 0X; Define(name, FALSE) END
205 END
206 END
207 END ReadDefines;
209 PROCEDURE CheckParams;
210 BEGIN
211 IF (exe # NIL) & (exe^ = "") THEN
212 Error("specified empty file name for exe", "", "", 0)
213 END;
214 IF target^ = "" THEN
215 Error("specified empty rule", "", "", 0)
216 END;
217 IF base^ = "" THEN
218 Error("specified empty path to cpmake rules", "", "", 0)
219 END;
220 ReadDefines
221 END CheckParams;
223 (* --------- loader --------- *)
225 PROCEDURE Import (m: Module; IN name: DevCPT.Name);
226 VAR i, j: INTEGER; imp: Module;
227 BEGIN
228 ASSERT(m # NIL, 20);
229 ASSERT(name # "", 21);
230 IF debugImport THEN Log.String(" import "); Log.String(name$) END;
231 IF name = "SYSTEM" THEN INCL(DevCPM.options, DevCPM.sysImp)
232 ELSIF name = "COM" THEN INCL(DevCPM.options, DevCPM.com)
233 ELSIF name = "JAVA" THEN INCL(DevCPM.options, DevCPM.java)
234 ELSE
235 IF debugImport THEN Log.Char(" ") END;
236 i := 0; (* find module in local list *)
237 WHILE (i < m.mno) & (m.imp[i].name$ # name$) DO INC(i) END;
238 IF i >= m.mno THEN
239 j := 0; (* find module in global list *)
240 WHILE (j < mno) & (modList[j].name$ # name$) DO INC(j) END;
241 IF j >= mno THEN
242 IF ~auto THEN
243 Log.String("module " + name + " required before " + m.name); Log.Ln; INC(werr)
244 END;
245 NEW(imp); imp.name := name$;
246 modList[mno] := imp; INC(mno)
247 ELSE
248 imp := modList[j]
249 END;
250 m.imp[m.mno] := imp; INC(m.mno)
251 ELSE DevCPM.err(1)
252 END
253 END;
254 IF debugImport THEN Log.Ln END;
255 END Import;
257 PROCEDURE ParseModule (m: Module);
258 VAR sym: BYTE; SelfName, impName, aliasName: DevCPT.Name;
260 PROCEDURE err (n: SHORTINT);
261 BEGIN DevCPM.err(n)
262 END err;
264 PROCEDURE CheckSym(s: SHORTINT);
265 BEGIN
266 IF sym = s THEN DevCPS.Get(sym) ELSE DevCPM.err(s) END
267 END CheckSym;
269 BEGIN
270 IF debugImport THEN Log.String("module " + m.name); Log.Ln END;
271 DevCPS.Init; DevCPS.Get(sym);
272 IF sym = module THEN DevCPS.Get(sym) ELSE err(16) END;
273 IF sym = ident THEN
274 SelfName := DevCPS.name$; DevCPS.Get(sym);
275 IF sym = lbrak THEN
276 INCL(DevCPM.options, DevCPM.interface); DevCPS.Get(sym);
277 IF sym = eql THEN DevCPS.Get(sym)
278 ELSE INCL(DevCPM.options, DevCPM.noCode)
279 END;
280 IF sym = string THEN INCL(m.flags, library); DevCPS.Get(sym)
281 ELSE err(string)
282 END;
283 CheckSym(rbrak)
284 END;
285 CheckSym(semicolon);
286 IF sym = import THEN DevCPS.Get(sym);
287 LOOP
288 IF sym = ident THEN
289 aliasName := DevCPS.name$; impName := aliasName$; DevCPS.Get(sym);
290 IF sym = becomes THEN DevCPS.Get(sym);
291 IF sym = ident THEN impName := DevCPS.name$; DevCPS.Get(sym) ELSE err(ident) END
292 END;
293 Import(m, impName)
294 ELSE err(ident)
295 END;
296 IF sym = comma THEN DevCPS.Get(sym)
297 ELSIF sym = ident THEN err(comma)
298 ELSE EXIT
299 END
300 END;
301 CheckSym(semicolon)
302 END;
303 LOOP (* preprocessor must read module fully *)
304 IF sym = end THEN
305 DevCPS.Get(sym);
306 IF sym = ident THEN
307 DevCPS.Get(sym);
308 IF sym = period THEN
309 IF DevCPS.name # SelfName THEN err(4) END;
310 EXIT
311 ELSIF sym = eof THEN
312 err(period);
313 EXIT
314 END
315 ELSIF sym = eof THEN
316 err(ident);
317 EXIT
318 END;
319 ELSIF sym = eof THEN
320 err(end);
321 EXIT
322 ELSE
323 DevCPS.Get(sym);
324 END
325 END
326 ELSE err(ident)
327 END;
328 DevCPS.str := NIL
329 END ParseModule;
331 PROCEDURE CheckModule (m: Module; source: String; OUT ok: BOOLEAN);
332 VAR s: Selector;
333 BEGIN
334 DevCPM.Init(source);
335 DevCPM.symList := m.dir;
336 (*DevCPM.codePath := m.outcode;*)
337 (*DevCPM.symPath := m.outsym;*)
338 DevCPM.name := m.name$;
339 INCL(DevCPM.options, DevCPM.comAware);
340 IF trap THEN
341 INCL(DevCPM.options, DevCPM.trap)
342 END;
343 (*IF oberon IN m.opts THEN INCL(DevCPM.options, DevCPM.oberon) END;*)
344 DevCPR.Init;
345 s := def.next;
346 WHILE s # NIL DO
347 DevCPR.Set(s.name, s.value);
348 s := s.next
349 END;
350 ParseModule(m);
351 DevCPR.Check;
352 ok := DevCPM.noerr;
353 DevCPR.Close;
354 DevCPM.InsertMarks;
355 DevCPM.Close;
356 Kernel.FastCollect
357 END CheckModule;
359 PROCEDURE MakePath (IN dir, name: Files.Name; IN type: Files.Type; OUT path: Files.Name);
360 BEGIN
361 ASSERT(name # "", 21);
362 IF dir = "" THEN path := modDir + "/" + name
363 ELSE path := dir + "/" + modDir + "/" + name
364 END;
365 Kernel.MakeFileName(path, type)
366 END MakePath;
368 PROCEDURE Open (loc: Files.Locator; IN sub, name: Files.Name; OUT path: Files.Name; OUT text: DswDocuments.Model);
369 VAR res: INTEGER;
370 BEGIN
371 ASSERT(loc # NIL, 20);
372 ASSERT(name # "", 21);
373 (* !!! use Kernel.MakeFileName instead ".ext" concat !!! *)
374 MakePath(sub, name, "cp", path);
375 DswDocuments.Open(loc, name + ".cp", text, res);
376 IF text = NIL THEN
377 MakePath(sub, name, "odc", path);
378 DswDocuments.Open(loc, name + ".odc", text, res);
379 IF (text = NIL) & (sub = "") THEN
380 MakePath(sysDir, name, "cp", path);
381 loc := Files.dir.This(sysDir).This(modDir);
382 DswDocuments.Open(loc, name + ".cp", text, res);
383 IF text = NIL THEN
384 MakePath(sysDir, name, "odc", path);
385 DswDocuments.Open(loc, name + ".odc", text, res);
386 IF text = NIL THEN
387 path := ""
388 END
389 END
390 END
391 END
392 END Open;
394 PROCEDURE GetSource (IN modName: ARRAY OF CHAR; list: DevCPM.Directory; OUT path: Files.Name; OUT s: String);
395 VAR
396 sub, name: Files.Name; loc: Files.Locator; base: DevCPM.Directory;
397 text: DswDocuments.Model; r: DswDocuments.Reader; i, res: INTEGER;
398 BEGIN
399 s := NIL; path := ""; base := list;
400 Kernel.SplitName(modName, sub, name);
401 loc := Files.dir.This(sub).This(modDir);
402 Open(loc, sub, name, path, text);
403 WHILE (text = NIL) & (base # NIL) DO
404 ASSERT(base.legacy, 100);
405 loc := Files.dir.This(base.path).This(sub).This(modDir);
406 Open(loc, sub, name, path, text);
407 base := base.next
408 END;
409 IF text # NIL THEN
410 NEW(s, text.Length() + 1);
411 IF s # NIL THEN
412 r := text.NewReader(NIL);
413 FOR i := 0 TO text.Length() - 1 DO
414 r.Read; s[i] := r.char
415 END
416 END
417 END
418 END GetSource;
420 PROCEDURE Trace (m, parent: Module; VAR lno: INTEGER);
421 VAR i: INTEGER;
422 BEGIN
423 IF ~(trace IN m.flags) THEN
424 INCL(m.flags, trace);
425 FOR i := 0 TO m.mno - 1 DO
426 Trace(m.imp[i], m, lno);
427 m.depth := MAX(m.depth, m.imp[i].depth + 1)
428 END;
429 IF ~(imported IN m.flags) THEN
430 INCL(m.flags, imported);
431 lnkList[lno] := m;
432 INC(lno)
433 END;
434 EXCL(m.flags, trace)
435 ELSE
436 Error("recursive import of ^0 in ^1", m.name$, parent.name$, 0)
437 END
438 END Trace;
440 PROCEDURE Sort;
441 VAR i, j: INTEGER; m: Module;
442 BEGIN
443 ASSERT((mno = 0) OR (lnkList[0] # NIL), 20);
444 cmpList := lnkList;
445 i := 1;
446 WHILE i < mno DO
447 m := cmpList[i];
448 j := i - 1;
449 WHILE (j >= 0) & (cmpList[j].depth > m.depth) DO
450 cmpList[j + 1] := cmpList[j];
451 DEC(j)
452 END;
453 cmpList[j + 1] := m;
454 INC(i)
455 END
456 END Sort;
458 PROCEDURE CheckDeps;
459 VAR i, j, num: INTEGER; m: Module; src: String; ok: BOOLEAN;
460 BEGIN
461 i := 0; rno := mno;
462 WHILE (err = 0) & (i < mno) DO
463 m := modList[i];
464 GetSource(m.name$, m.dir, m.odc, src);
465 IF src # NIL THEN
466 CheckModule(m, src, ok);
467 IF ~ok THEN INC(err) END
468 ELSE
469 Error("unable to open module ^1", m.name$, "", 0)
470 END;
471 INC(i)
472 END;
473 INC(err, werr);
474 num := 0;
475 FOR i := 0 TO rno - 1 DO
476 Trace(modList[i], modList[i], num)
477 END;
478 ASSERT((err # 0) OR (num = mno), 100);
479 Sort;
480 IF debugOrder THEN
481 Log.String("Parallel depth:"); Log.Ln;
482 FOR i := 0 TO mno - 1 DO
483 Log.String(" " + cmpList[i].name); Log.Int(cmpList[i].depth); Log.Ln;
484 END
485 END
486 END CheckDeps;
488 PROCEDURE IsCompiled (m: Module): BOOLEAN;
489 CONST target = {hasSym, hasObj};
490 VAR i: INTEGER; ready: BOOLEAN;
491 BEGIN
492 ASSERT(m # NIL, 20);
493 i := 0;
494 ready := ~(hasErrors IN m.flags) & (m.flags * target = target);
495 WHILE ready & (i < m.mno) DO
496 ready := IsCompiled(m.imp[i]);
497 INC(i)
498 END;
499 RETURN ready
500 END IsCompiled;
502 PROCEDURE Ready (m: Module): BOOLEAN;
503 CONST target = {hasSym, hasObj};
504 VAR i: INTEGER; ready: BOOLEAN;
505 BEGIN
506 i := 0;
507 ready := ~(hasErrors IN m.flags) & (m.flags * target # target) & (m.worker = NIL);
508 WHILE ready & (i < m.mno) DO
509 ready := IsCompiled(m.imp[i]);
510 INC(i)
511 END;
512 RETURN ready
513 END Ready;
515 PROCEDURE PrepareCompiler (m: Module): DswProcs.Process;
516 VAR p: DswProcs.Process; s: Selector;
517 BEGIN
518 ASSERT(m # NIL, 20);
519 ASSERT(m.odc # "", 21);
520 ASSERT(m.worker = NIL, 22);
521 p := DswProcs.dir.New();
522 p.Program(base + "/" + target + "/" + "build");
523 p.PutParam(m.odc);
524 RETURN p
525 END PrepareCompiler;
527 PROCEDURE Compile;
528 VAR i, j, num, res: INTEGER; ok: BOOLEAN; m: Module; w: DswProcs.Process;
529 BEGIN
530 IF mno = 0 THEN RETURN END;
531 num := 0; j := 0;
532 WHILE (err = 0) & (num < mno) OR (j > 0) DO
533 i := 0;
534 WHILE (err = 0) & (i < mno) & (j < jobs) DO
535 m := cmpList[i];
536 IF Ready(m) THEN
537 w := PrepareCompiler(m);
538 IF debugJobs THEN Log.String("Start job " + m.name) END;
539 w.Execute(ok);
540 IF ok THEN
541 IF debugJobs THEN Log.String(" ok") END;
542 m.worker := w;
543 INC(j)
544 ELSE
545 IF debugJobs THEN Log.String(" fail") END;
546 INCL(m.flags, hasErrors);
547 INC(err)
548 END;
549 IF debugJobs THEN Log.Ln END
550 END;
551 INC(i)
552 END;
553 WHILE (err = 0) & (j >= jobs) OR (j > 0) DO
554 i := 0;
555 WHILE (j > 0) & (i < mno) DO
556 m := cmpList[i];
557 w := m.worker;
558 IF (w # NIL) & w.IsTerminated() THEN
559 res := w.Result();
560 IF debugJobs THEN Log.String("Stop job " + m.name); Log.Int(res); Log.Ln END;
561 IF res = 0 THEN
562 INCL(m.flags, hasObj);
563 INCL(m.flags, hasSym);
564 INC(num)
565 ELSE
566 INCL(m.flags, hasErrors);
567 INC(err)
568 END;
569 m.worker := NIL;
570 DEC(j)
571 END;
572 INC(i)
573 END
574 END
575 END
576 END Compile;
578 PROCEDURE Link;
579 VAR p: DswProcs.Process; ok: BOOLEAN; i, res: INTEGER;
580 BEGIN
581 IF exe # NIL THEN
582 p := DswProcs.dir.New();
583 p.Program(base + "/" + target + "/" + "link");
584 p.PutParam(exe$);
585 FOR i := 0 TO mno - 1 DO
586 IF ~(library IN lnkList[i].flags) THEN
587 p.PutParam(lnkList[i].name$)
588 END
589 END;
590 p.Execute(ok);
591 IF ok THEN
592 p.Wait;
593 res := p.Result();
594 IF res # 0 THEN
595 Error("link failed with code ^2", "", "", res)
596 END
597 ELSE Error("link failed: unable to execute script", "", "", 0)
598 END
599 END
600 END Link;
602 PROCEDURE Cleanup;
603 VAR p: DswProcs.Process; ok: BOOLEAN; res: INTEGER;
604 BEGIN
605 IF clean THEN
606 p := DswProcs.dir.New();
607 p.Program(base + "/" + target + "/" + "clean");
608 p.Execute(ok);
609 IF ok THEN
610 p.Wait;
611 res := p.Result();
612 IF res # 0 THEN
613 Error("cleanup failed with code ^2", "", "", res)
614 END
615 ELSE Error("cleanup failed: unable to execute script", "", "", 0)
616 END
617 END
618 END Cleanup;
620 PROCEDURE Main;
621 BEGIN
622 IF Kernel.trapCount = 0 THEN
623 ParseArgs;
624 IF err = 0 THEN
625 CheckParams;
626 IF err = 0 THEN
627 CheckDeps;
628 IF err = 0 THEN
629 Cleanup;
630 IF err = 0 THEN
631 Compile;
632 IF err = 0 THEN
633 Link
634 END
635 END
636 END
637 END
638 END
639 ELSE Error("trap occured", "", "", 0)
640 END;
641 IF err = 0 THEN Kernel.Quit(0)
642 ELSE Kernel.Quit(1)
643 END
644 END Main;
646 BEGIN
647 NEW(def);
648 ASSERT(def # NIL, 100);
649 Kernel.intTrap := FALSE;
650 Kernel.Start(Main)
651 END DswMakeMain.