1structure Holmake_tools :> Holmake_tools =
2struct
3
4
5open Systeml
6open Holmake_tools_dtype
7
8fun K x y = x
9
10structure Path = OS.Path
11structure FileSys = OS.FileSys
12
13val DEFAULT_OVERLAY = "Overlay.ui"
14
15fun normPath s = OS.Path.toString(OS.Path.fromString s)
16fun itstrings f [] = raise Fail "itstrings: empty list"
17  | itstrings f [x] = x
18  | itstrings f (h::t) = f h (itstrings f t);
19fun fullPath slist = normPath
20   (itstrings (fn chunk => fn path => OS.Path.concat (chunk,path)) slist);
21
22val spacify = String.concatWith " "
23fun nspaces f n = if n <= 0 then () else (f " "; nspaces f (n - 1))
24fun collapse_bslash_lines s = let
25  val charlist = explode s
26  fun trans [] = []
27    | trans (#"\\"::(#"\n"::rest)) = trans rest
28    | trans (x::xs) = x :: trans xs
29in
30  implode (trans charlist)
31end
32
33val kernelid_fname = Path.concat(HOLDIR, ".kernelidstr")
34fun checkterm pfx s =
35  case OS.Process.getEnv "TERM" of
36      NONE => s
37    | SOME term =>
38      if String.isPrefix "xterm" term orelse term = "screen" then
39        pfx ^ s ^ "\027[0m"
40      else
41        s
42
43val bold = checkterm "\027[1m"
44val boldred = checkterm "\027[31m\027[1m"
45val boldgreen = checkterm "\027[32m\027[1m"
46val boldyellow = checkterm "\027[33m\027[1m"
47val red = checkterm "\027[31m"
48val dim = checkterm "\027[2m"
49val CLR_EOL = "\027[0K" (* ANSI clear to EOL code *)
50
51fun optbind x f =
52  case x of
53      SOME y => f y
54    | _ => NONE
55fun optchoice (opt1, opt2) =
56  case opt1 of
57      NONE => opt2
58    | _ => opt1
59infix ++
60val op++ = optchoice
61fun isc r = Int.scan StringCvt.DEC r
62fun char_reader (s, i) = if size s <= i then NONE
63                         else SOME (String.sub(s,i), (s,i+1))
64fun estbind m f s =
65  case m s of
66      NONE => NONE
67    | SOME(r,s') => f r s'
68
69fun run s =
70  let
71    val outfile = OS.FileSys.tmpName()
72    val res = OS.Process.system (String.concatWith " " [s,"1>",outfile,"2>&1"])
73    val output =
74        let
75          val istrm = TextIO.openIn outfile
76        in
77          TextIO.inputAll istrm before TextIO.closeIn istrm
78        end handle IO.Io _ => ""
79  in
80    if OS.Process.isSuccess res then SOME output else NONE
81  end
82
83fun optassert P x = if P x then SOME x else NONE
84
85fun getWidth0 () =
86  let
87    fun tputresult istr = Int.fromString istr
88    fun sttyresult i2str =
89      Option.map #1
90                 (estbind (isc char_reader) (fn _ => isc char_reader)
91                          (i2str, 0))
92    fun positive m x = optbind (m x) (optassert (fn i => i > 0))
93  in
94    optbind (run "stty size") (positive sttyresult) ++
95    optbind (run "tput cols") (positive tputresult) ++
96    SOME 80
97  end
98
99fun getWidth() = valOf (getWidth0())
100
101fun realspace_delimited_fields s = let
102  open Substring
103  fun inword cword words ss =
104      case getc ss of
105        NONE => List.rev (implode (List.rev cword) :: words)
106      | SOME (c,ss') => let
107        in
108          case c of
109            #" " => outword (implode (List.rev cword) :: words) ss'
110          | #"\\" => let
111            in
112              case getc ss' of
113                NONE => List.rev (implode (List.rev (c::cword)) :: words)
114              | SOME (c',ss'') => inword (c'::cword) words ss''
115            end
116          | _ => inword (c::cword) words ss'
117        end
118  and outword words ss =
119      case getc ss of
120        NONE => List.rev words
121      | SOME(c, ss') => let
122        in
123          case c of
124            #" " => outword words ss'
125          | _ => inword [] words ss
126        end
127in
128  outword [] (full s)
129end
130
131type output_functions = {warn : string -> unit,
132                         info : string -> unit,
133                         chatty : string -> unit,
134                         tgtfatal : string -> unit,
135                         diag : (unit -> string) -> unit}
136
137fun die_with message = let
138  open TextIO
139in
140  output(stdErr, message ^ "\n");
141  flushOut stdErr;
142  OS.Process.exit OS.Process.failure
143end
144
145fun shorten_name name =
146  if OS.Path.file name = "Holmake" then "Holmake" else name
147
148fun output_functions {usepfx,chattiness=n} = let
149  val execname = if usepfx then shorten_name (CommandLine.name()) ^ ": " else ""
150  open TextIO
151  fun msg strm s =
152    if s = "" then ()
153    else
154      let
155        val ss = Substring.full s
156        val (pfx_ss,sfx_ss) = Substring.splitl Char.isSpace ss
157        val pfx = Substring.string pfx_ss
158        val sfx = Substring.string sfx_ss
159      in
160        output(strm, pfx ^ execname ^ sfx^"\n");
161        flushOut strm
162      end
163  fun donothing _ = ()
164  val warn = if n >= 1 then msg stdErr else donothing
165  val info = if n >= 1 then msg stdOut else donothing
166  val chatty = if n >= 2 then msg stdOut else donothing
167  val tgtfatal = msg stdErr
168  val diag = if n >= 3 then (fn sf => msg stdErr (sf())) else donothing
169in
170  {warn = warn, diag = diag, tgtfatal = tgtfatal, info = info, chatty = chatty}
171end
172
173fun exists_readable s = OS.FileSys.access(s, [OS.FileSys.A_READ])
174
175fun check_distrib toolname = let
176  val fpath = fullPath
177  open FileSys
178  fun checkdir () =
179    access ("sigobj", [A_READ, A_EXEC]) andalso
180    isDir "sigobj" andalso
181    access (fpath ["bin", "Holmake"], [A_READ, A_EXEC])
182  fun traverse () = let
183    val d = getDir()
184  in
185    if checkdir() then SOME (fpath [d, "bin", toolname])
186    else if Path.isRoot d then NONE
187    else (chDir Path.parentArc; traverse())
188  end
189  val start = getDir()
190in
191  traverse() before chDir start
192end
193
194fun do_lastmade_checks (ofns : output_functions) {no_lastmakercheck} = let
195  val {warn,diag,...} = ofns
196  val mypath = find_my_path()
197  val _ = diag (K ("running "^mypath))
198  fun write_lastmaker_file () = let
199    val outstr = TextIO.openOut ".HOLMK/lastmaker"
200  in
201    TextIO.output(outstr, mypath ^ "\n");
202    TextIO.closeOut outstr
203  end handle IO.Io _ => ()
204
205  fun lmfile() =
206      if not no_lastmakercheck andalso
207         FileSys.access (".HOLMK/lastmaker", [FileSys.A_READ])
208      then let
209          val _ = diag (K "Found a lastmaker file to look at.")
210          val istrm = TextIO.openIn ".HOLMK/lastmaker"
211        in
212          case TextIO.inputLine istrm of
213            NONE => (warn "Empty Last Maker file";
214                     TextIO.closeIn istrm;
215                     write_lastmaker_file())
216          | SOME s => let
217              open Substring
218              val path = string (dropr Char.isSpace (full s))
219              val _ = TextIO.closeIn istrm
220            in
221              if FileSys.access (path, [FileSys.A_READ, FileSys.A_EXEC])
222              then
223                if mypath = path then ()
224                else
225                  (warn ("*** Switching to execute "^path);
226                   warn ("*** (Honouring last Holmake call in this directory)");
227                   Systeml.exec(path,
228                                path::"--nolmbc"::CommandLine.arguments()))
229              else (warn "Garbage in Last Maker file";
230                    write_lastmaker_file())
231            end
232        end
233      else write_lastmaker_file()
234in
235  diag (K "Looking to see if I am in a HOL distribution.");
236  case check_distrib "Holmake" of
237    NONE => let
238    in
239      diag (K "Not in a HOL distribution");
240      lmfile()
241    end
242  | SOME p =>
243    if p = mypath then diag (K "In the right HOL distribution")
244    else if no_lastmakercheck then
245      diag (K "In the wrong distribution, but --nolmbc takes precedence.")
246    else
247      (warn ("*** Switching to execute "^p);
248       warn ("*** (As we are in/under its HOLDIR)");
249       Systeml.exec (p, p::"--nolmbc"::CommandLine.arguments()))
250end
251
252fun string_part0 (Theory s) = s
253  | string_part0 (Script s) = s
254  | string_part0 (Other s) = s
255fun string_part1 (RawArticle s) = s
256  | string_part1 (ProcessedArticle s) = s
257fun string_part (UO c)  = string_part0 c
258  | string_part (UI c)  = string_part0 c
259  | string_part (SML c) = string_part0 c
260  | string_part (SIG c) = string_part0 c
261  | string_part (ART c) = string_part1 c
262  | string_part (DAT s) = s
263  | string_part (Unhandled s) = s
264
265fun isProperSuffix s1 s2 =
266    if size s1 < size s2 andalso String.isSuffix s1 s2 then
267      SOME (String.substring(s2,0,size s2 - size s1))
268    else NONE
269
270fun toCodeType s = let
271  val possprefix = isProperSuffix "Theory" s
272in
273  if (isSome possprefix) then Theory (valOf possprefix)
274  else let
275    val possprefix = isProperSuffix "Script" s
276  in
277    if isSome possprefix then Script (valOf possprefix)
278    else Other s
279  end
280end
281
282fun toArticleType s = let
283  val possprefix = isProperSuffix ".ot" s
284in
285  if (isSome possprefix) then ProcessedArticle (valOf possprefix)
286  else RawArticle s
287end
288
289fun toFile s0 = let
290  val {base = s, ext} = OS.Path.splitBaseExt s0
291in
292  case ext of
293    SOME "sml" => SML (toCodeType s)
294  | SOME "sig" => SIG (toCodeType s)
295  | SOME "uo"  => UO (toCodeType s)
296  | SOME "ui"  => UI (toCodeType s)
297  | SOME "art" => ART (toArticleType s)
298  | SOME "dat" => if String.isSuffix "Theory" s then
299                    DAT (String.extract(s,0,SOME(size s - 6)))
300                  else Unhandled s0
301  |    _       => Unhandled s0
302end
303
304fun extract_theory slist =
305  case slist of
306      [] => NONE
307    | s :: rest => (case toFile s of
308                        SML (Theory thy) => SOME thy
309                      | _ => extract_theory rest)
310
311fun codeToString c =
312  case c of
313    Theory s => s ^ "Theory"
314  | Script s => s ^ "Script"
315  | Other s  => s
316
317fun articleToString c =
318  case c of
319    RawArticle s => s
320  | ProcessedArticle s => s ^ ".ot"
321
322fun fromFile f =
323  case f of
324    UO c  => codeToString c ^ ".uo"
325  | UI c  => codeToString c ^ ".ui"
326  | SIG c => codeToString c ^ ".sig"
327  | SML c => codeToString c ^ ".sml"
328  | ART c => articleToString c ^ ".art"
329  | DAT s => s ^ "Theory.dat"
330  | Unhandled s => s
331
332fun fromFileNoSuf f =
333  case f of
334    UO c  => codeToString c
335  | UI c  => codeToString c
336  | SIG c => codeToString c
337  | SML c => codeToString c
338  | ART a => articleToString a
339  | DAT s => s
340  | Unhandled s => s
341
342fun member m [] = false
343  | member m (x::xs) = if x = m then true else member m xs
344fun set_union s1 s2 =
345  case s1 of
346    [] => s2
347  | (e::es) => let
348      val s' = set_union es s2
349    in
350      if member e s' then s' else e::s'
351    end
352fun delete m [] = []
353  | delete m (x::xs) = if m = x then delete m xs else x::delete m xs
354fun set_diff s1 s2 = foldl (fn (s2e, s1') => delete s2e s1') s1 s2
355fun remove_duplicates [] = []
356  | remove_duplicates (x::xs) = x::(remove_duplicates (delete x xs))
357
358
359
360fun file_compare (f1, f2) = String.compare (fromFile f1, fromFile f2)
361
362fun primary_dependent f =
363    case f of
364      UO c => SOME (SML c)
365    | UI c => SOME (SIG c)
366    | SML (Theory s) => SOME (SML (Script s))
367    | SIG (Theory s) => SOME (SML (Script s))
368    | DAT s => SOME (SML (Script s))
369    | ART (RawArticle s) => SOME (SML (Script s))
370    | ART (ProcessedArticle s) => SOME (ART (RawArticle s))
371    | _ => NONE
372
373fun read_files ds P action =
374    case OS.FileSys.readDir ds of
375      NONE => OS.FileSys.closeDir ds
376    | SOME nextfile =>
377      (if P nextfile then action nextfile else ();
378       read_files ds P action)
379
380fun quiet_remove s = OS.FileSys.remove s handle e => ()
381
382fun clean_dir {extra_cleans} = let
383  val cdstream = OS.FileSys.openDir "."
384  fun to_delete f =
385      case (toFile f) of
386        UO _ => true
387      | UI _ => true
388      | SIG (Theory _) => true
389      | SML (Theory _) => true
390      | SML (Script s) =>
391          (case OS.Path.ext s of SOME "art" => true | _ => false)
392      | DAT _ => true
393      | ART _ => true
394      | _ => false
395in
396  read_files cdstream to_delete quiet_remove;
397  app quiet_remove extra_cleans
398end
399
400fun clean_forReloc {holheap} =
401  if Systeml.ML_SYSNAME = "poly" then
402    case holheap of SOME s => quiet_remove s | _ => ()
403  else ()
404
405exception DirNotFound
406fun clean_depdir {depdirname} = let
407  val depds = OS.FileSys.openDir depdirname handle
408      OS.SysErr _ => raise DirNotFound
409in
410  read_files depds
411             (fn _ => true)
412             (fn s => OS.FileSys.remove (fullPath [depdirname, s]));
413  OS.FileSys.rmDir depdirname;
414  true
415end handle OS.SysErr (mesg, _) => let
416           in
417             print ("make cleanDeps failed with message: "^mesg^"\n");
418             false
419           end
420         | DirNotFound => true
421
422val nice_dir =
423    case OS.Process.getEnv "HOME" of
424        SOME h => (fn s => if String.isPrefix h s then
425                             "~" ^ String.extract(s,size h,NONE)
426                           else s)
427      | NONE => (fn s => s)
428
429fun xterm_log s =
430  ignore (OS.Process.system ("/bin/sh -c 'printf \"\\033]0;" ^ s ^ "\\007\"'"))
431
432val terminal_log =
433    if Systeml.isUnix then xterm_log
434    else (fn s => ())
435
436structure hmdir =
437struct
438
439type t = {absdir : string, relpath : string option}
440
441fun op+ (d, e) = Path.mkCanonical (Path.concat(d, e))
442
443fun curdir () = {relpath = SOME (OS.Path.currentArc),
444                 absdir = OS.FileSys.getDir()}
445
446fun compare ({absdir = d1, ...} : t, {absdir = d2, ...} : t) =
447    String.compare (d1, d2)
448
449fun toString {relpath,absdir} =
450    case relpath of
451        NONE => absdir
452      | SOME p => p
453
454fun toAbsPath {relpath,absdir} = absdir
455
456fun pretty_dir d =
457  let
458    val abs = toAbsPath d
459    val abs' = holpathdb.reverse_lookup {path=abs}
460  in
461    if abs = abs' then toString d else abs'
462  end
463
464fun fromPath {origin,path} =
465    if Path.isAbsolute path then
466      {relpath = NONE, absdir = Path.mkCanonical path}
467    else
468      {relpath = SOME path, absdir = origin + path}
469
470fun extendp {base = {relpath, absdir}, extension} = let
471  val relpath_str = case relpath of NONE => "NONE"
472                                  | SOME s => "SOME("^s^")"
473in
474  if Path.isAbsolute extension then
475    {relpath = NONE, absdir = Path.mkCanonical extension}
476  else
477      case relpath of
478          NONE => {absdir = absdir + extension, relpath = NONE}
479        | SOME p => {relpath = SOME (p + extension),
480                     absdir = absdir + extension}
481end
482
483fun extend {base, extension} =
484    extendp {base = base, extension = toString extension}
485
486fun sort l = let
487  fun foldthis1 ({absdir,relpath}, acc) =
488      case Binarymap.peek (acc, absdir) of
489          NONE => Binarymap.insert(acc, absdir, relpath)
490        | SOME NONE => Binarymap.insert(acc, absdir, relpath)
491        | _ => acc
492  val m = foldl foldthis1 (Binarymap.mkDict String.compare) l
493  fun foldthis2 (abs,rel,acc) = {absdir = abs, relpath = rel} :: acc
494in
495  Binarymap.foldr foldthis2 [] m
496end
497
498end (* hmdir struct *)
499
500fun process_hypat_options s = let
501  open Substring
502  val ss = full s
503  fun recurse (noecho, ignore_error, ss) =
504      if noecho andalso ignore_error then
505        {noecho = true, ignore_error = true,
506         command = string (dropl (fn c => c = #"@" orelse c = #"-") ss)}
507      else
508        case getc ss of
509          NONE => {noecho = noecho, ignore_error = ignore_error,
510                   command = ""}
511        | SOME (c, ss') =>
512          if c = #"@" then recurse (true, ignore_error, ss')
513          else if c = #"-" then recurse (noecho, true, ss')
514          else { noecho = noecho, ignore_error = ignore_error,
515                 command = string ss }
516in
517  recurse (false, false, ss)
518end
519
520local
521  fun split p = let val {base, ext} = OS.Path.splitBaseExt p in (base, ext) end
522in
523  fun target_string l =
524    let
525      val (names, e) = ListPair.unzip (List.map split l)
526      val exts = List.mapPartial (fn x => x) e
527      val n = List.length exts
528    in
529      case names of
530         [] => ""
531       | [_] => List.hd l
532       | h :: t => if List.all (fn x => x = h) t andalso List.length e = n
533                     then if n = 2 andalso String.isSuffix "Theory" h
534                            then h
535                          else h ^ ".{" ^ String.concatWith "," exts ^ "}"
536                   else String.concatWith " " l
537    end
538end
539
540type include_info = {includes : string list, preincludes : string list }
541
542type include_info = {includes : string list, preincludes : string list}
543type dirset = hmdir.t Binaryset.set
544
545val empty_dirset = Binaryset.empty hmdir.compare
546type incset_pair = {pres : dirset, incs : dirset}
547type incdirmap = (hmdir.t,incset_pair) Binarymap.dict
548val empty_incdirmap = Binarymap.mkDict hmdir.compare
549
550type holmake_dirinfo = {
551  visited : hmdir.t Binaryset.set,
552  incdirmap : incdirmap
553}
554type holmake_result = holmake_dirinfo option
555
556fun find_files ds P =
557  let
558    fun recurse acc =
559      case OS.FileSys.readDir ds of
560          NONE => (OS.FileSys.closeDir ds; List.rev acc)
561        | SOME fname => if P fname then recurse (fname::acc)
562                        else recurse acc
563  in
564    recurse []
565  end
566
567fun generate_all_plausible_targets warn first_target =
568    case first_target of
569        SOME s => [toFile s]
570      | NONE =>
571        let
572          val cds = OS.FileSys.openDir "."
573          fun not_a_dot f = not (String.isPrefix "." f)
574          fun ok_file f =
575              case (toFile f) of
576                  SIG (Theory _) => false
577                | SIG _ => true
578                | SML (Script s) =>
579                  (case OS.Path.ext s of
580                       SOME "art" => false
581                       (* can be generated as temporary by opentheory
582                          machinery *)
583                     | SOME _ =>
584                         (warn ("Theory names (e.g., "^f^
585                                ") can't include '.' characters");
586                          false)
587                     | NONE => true)
588                | SML (Theory _) => false
589                | SML _ => true
590                | _ => false
591          val src_files = find_files cds (fn s => ok_file s andalso not_a_dot s)
592          fun src_to_target (SIG (Script s)) = UO (Theory s)
593            | src_to_target (SML (Script s)) = UO (Theory s)
594            | src_to_target (SML s) = (UO s)
595            | src_to_target (SIG s) = (UI s)
596            | src_to_target _ = raise Fail "Can't happen"
597          val initially = map (src_to_target o toFile) src_files
598          fun remove_sorted_dups [] = []
599            | remove_sorted_dups [x] = [x]
600            | remove_sorted_dups (x::y::z) = if x = y then remove_sorted_dups (y::z)
601                                             else x :: remove_sorted_dups (y::z)
602        in
603          remove_sorted_dups (Listsort.sort file_compare initially)
604        end
605
606(* dependency analysis *)
607exception HolDepFailed
608fun runholdep {ofs, extras, includes, arg, destination} = let
609  val {chatty, diag, warn, ...} : output_functions = ofs
610  val diagK = diag o K
611  val _ = chatty ("Analysing "^fromFile arg)
612  fun buildables s = let
613    val f = toFile s
614    val files =
615        case f of
616          SML (ss as Script t) => [UI ss, UO ss, SML (Theory t), DAT t,
617                                   SIG (Theory t), UI (Theory t),
618                                   UO (Theory t), ART (RawArticle t), f]
619        | SML ss => [UI ss, UO ss, f]
620        | SIG ss => [UI ss, f]
621        | ART (RawArticle s) => [ART (ProcessedArticle s), f]
622        | x => [x]
623  in
624    map fromFile files
625  end
626  val buildable_extras = List.concat (map buildables extras)
627  val _ = diagK ("Running Holdep on "^fromFile arg^" with includes = [" ^
628                 String.concatWith ", " includes ^ "], assumes = [" ^
629                 String.concatWith ", " buildable_extras ^"]")
630  val holdep_result =
631    Holdep.main {assumes = buildable_extras, diag = diag,
632                 includes = includes, fname = fromFile arg}
633    handle Holdep.Holdep_Error s =>
634             (warn ("Holdep failed: "^s); raise HolDepFailed)
635         | e => (warn ("Holdep exception: "^General.exnMessage e);
636                 raise HolDepFailed)
637  fun myopen s =
638    if FileSys.access(DEPDIR, []) then
639      if FileSys.isDir DEPDIR then TextIO.openOut s
640      else die_with ("Want to put dependency information in directory "^
641                     DEPDIR^", but it already exists as a file")
642    else
643     (chatty ("Trying to create directory "^DEPDIR^" for dependency files");
644      FileSys.mkDir DEPDIR;
645      TextIO.openOut s
646     )
647  open TextIO
648  val outstr = myopen (normPath destination)
649in
650  output(outstr, Holdep.encode_for_HOLMKfile holdep_result);
651  closeOut outstr
652end
653
654(* pull out a list of files that target depends on from depfile.  *)
655(* All files on the right of a colon are assumed to be dependencies.
656   This is despite the fact that holdep produces two entries when run
657   on fooScript.sml files, one for fooScript.uo, and another for fooScript
658   itself, we actually want all of those dependencies in one big chunk
659   because the production of fooTheory.{sig,sml} is done as one
660   atomic step from fooScript.sml. *)
661fun first f [] = NONE
662  | first f (x::xs) = case f x of NONE => first f xs | res => res
663
664fun get_dependencies_from_file depfile = let
665  fun get_whole_file s = let
666    open TextIO
667    val instr = openIn (normPath s)
668  in
669    inputAll instr before closeIn instr
670  end
671  fun parse_result s = let
672    val lines = String.fields (fn c => c = #"\n") (collapse_bslash_lines s)
673    fun process_line line = let
674      val (lhs0, rhs0) = Substring.splitl (fn c => c <> #":")
675                                          (Substring.full line)
676      val lhs = Substring.string lhs0
677      val rhs = Substring.string (Substring.slice(rhs0, 1, NONE))
678        handle Subscript => ""
679    in
680      realspace_delimited_fields rhs
681    end
682    val result = List.concat (map process_line lines)
683  in
684    List.map toFile result
685  end
686in
687  parse_result (get_whole_file depfile)
688end
689
690(* a function that given a product file, figures out the argument that
691   should be passed to runholdep in order to get back secondary
692   dependencies. *)
693
694fun holdep_arg (UO c) = SOME (SML c)
695  | holdep_arg (UI c) = SOME (SIG c)
696  | holdep_arg (SML (Theory s)) = SOME (SML (Script s))
697  | holdep_arg (SIG (Theory s)) = SOME (SML (Script s))
698  | holdep_arg (DAT s) = SOME (SML (Script s))
699  | holdep_arg (ART (RawArticle s)) = SOME (SML (Script s))
700  | holdep_arg (ART (ProcessedArticle s)) = SOME (ART (RawArticle s))
701  | holdep_arg _ = NONE
702
703fun mk_depfile_name DEPDIR s = fullPath [DEPDIR, s^".d"]
704
705infix forces_update_of
706fun (f1 forces_update_of f2) = let
707  open Time
708in
709  FileSys.access(f1, []) andalso
710  (not (FileSys.access(f2, [])) orelse FileSys.modTime f1 > FileSys.modTime f2)
711end
712
713
714fun get_direct_dependencies {incinfo,DEPDIR,output_functions,extra_targets} f =
715let
716  val fname = fromFile f
717  val arg = holdep_arg f  (* arg is file to analyse for dependencies *)
718  val {includes,preincludes} = incinfo
719in
720  if isSome arg then let
721    val arg = valOf arg
722    val argname = fromFile arg
723    val depfile = mk_depfile_name DEPDIR argname
724    val _ =
725      if argname forces_update_of depfile then
726        runholdep {ofs = output_functions, extras = extra_targets,
727                   includes = preincludes @ includes, arg = arg,
728                   destination = depfile}
729      else ()
730    val phase1 =
731      (* circumstances can arise in which the dependency file won't be
732         built, and won't exist; mainly because the file we're trying to
733         compute dependencies for doesn't exist either.  In this case, we
734         can only return the empty list *)
735      if exists_readable depfile then
736        get_dependencies_from_file depfile
737      else
738        []
739  in
740    case f of
741        UO (Theory s) => UI (Theory s) :: DAT s :: phase1
742      | UO x =>
743        if FileSys.access(fromFile (SIG x), []) andalso
744           List.all (fn f => f <> SIG x) phase1
745        then
746          UI x :: phase1
747        else
748          phase1
749      | _ => phase1
750  end
751  else
752    []
753end
754
755
756
757end (* struct *)
758