comparison vext.sml @ 1772:128c4544036d

Update Vext
author Chris Cannam
date Fri, 09 Mar 2018 09:00:48 +0000
parents 762ef5d2722a
children 316c4fd7e7bc
comparison
equal deleted inserted replaced
1771:bd14a0f69b60 1772:128c4544036d
36 shall not be used in advertising or otherwise to promote the sale, 36 shall not be used in advertising or otherwise to promote the sale,
37 use or other dealings in this Software without prior written 37 use or other dealings in this Software without prior written
38 authorization. 38 authorization.
39 *) 39 *)
40 40
41 val vext_version = "0.9.95" 41 val vext_version = "0.9.97"
42 42
43 43
44 datatype vcs = 44 datatype vcs =
45 HG | 45 HG |
46 GIT | 46 GIT |
140 val archive_dir = ".vext-archive" 140 val archive_dir = ".vext-archive"
141 end 141 end
142 142
143 signature VCS_CONTROL = sig 143 signature VCS_CONTROL = sig
144 144
145 (** Check whether the given VCS is installed and working *)
146 val is_working : context -> bool result
147
145 (** Test whether the library is present locally at all *) 148 (** Test whether the library is present locally at all *)
146 val exists : context -> libname -> bool result 149 val exists : context -> libname -> bool result
147 150
148 (** Return the id (hash) of the current revision for the library *) 151 (** Return the id (hash) of the current revision for the library *)
149 val id_of : context -> libname -> id_or_tag result 152 val id_of : context -> libname -> id_or_tag result
192 signature LIB_CONTROL = sig 195 signature LIB_CONTROL = sig
193 val review : context -> libspec -> (libstate * localstate) result 196 val review : context -> libspec -> (libstate * localstate) result
194 val status : context -> libspec -> (libstate * localstate) result 197 val status : context -> libspec -> (libstate * localstate) result
195 val update : context -> libspec -> unit result 198 val update : context -> libspec -> unit result
196 val id_of : context -> libspec -> id_or_tag result 199 val id_of : context -> libspec -> id_or_tag result
200 val is_working : context -> vcs -> bool result
197 end 201 end
198 202
199 structure FileBits :> sig 203 structure FileBits :> sig
200 val extpath : context -> string 204 val extpath : context -> string
201 val libpath : context -> libname -> string 205 val libpath : context -> libname -> string
578 | OK () => update' () 582 | OK () => update' ()
579 end 583 end
580 584
581 fun id_of context ({ libname, ... } : libspec) = 585 fun id_of context ({ libname, ... } : libspec) =
582 V.id_of context libname 586 V.id_of context libname
587
588 fun is_working context vcs =
589 V.is_working context
583 590
584 end 591 end
585 592
586 (* Simple Standard ML JSON parser 593 (* Simple Standard ML JSON parser
587 https://bitbucket.org/cannam/sml-simplejson 594 https://bitbucket.org/cannam/sml-simplejson
1160 given in the project file changes. *) 1167 given in the project file changes. *)
1161 1168
1162 type vcsstate = { id: string, modified: bool, 1169 type vcsstate = { id: string, modified: bool,
1163 branch: string, tags: string list } 1170 branch: string, tags: string list }
1164 1171
1172 val hg_program = "hg"
1173
1165 val hg_args = [ "--config", "ui.interactive=true", 1174 val hg_args = [ "--config", "ui.interactive=true",
1166 "--config", "ui.merge=:merge" ] 1175 "--config", "ui.merge=:merge" ]
1167 1176
1168 fun hg_command context libname args = 1177 fun hg_command context libname args =
1169 FileBits.command context libname ("hg" :: hg_args @ args) 1178 FileBits.command context libname (hg_program :: hg_args @ args)
1170 1179
1171 fun hg_command_output context libname args = 1180 fun hg_command_output context libname args =
1172 FileBits.command_output context libname ("hg" :: hg_args @ args) 1181 FileBits.command_output context libname (hg_program :: hg_args @ args)
1173 1182
1183 fun is_working context =
1184 case hg_command_output context "" ["--version"] of
1185 OK "" => OK false
1186 | OK _ => OK true
1187 | ERROR e => ERROR e
1188
1174 fun exists context libname = 1189 fun exists context libname =
1175 OK (OS.FileSys.isDir (FileBits.subpath context libname ".hg")) 1190 OK (OS.FileSys.isDir (FileBits.subpath context libname ".hg"))
1176 handle _ => OK false 1191 handle _ => OK false
1177 1192
1178 fun remote_for context (libname, source) = 1193 fun remote_for context (libname, source) =
1311 (vext/master). The remote we use is always named vext, and we 1326 (vext/master). The remote we use is always named vext, and we
1312 update it to the expected URL each time we fetch, in order to 1327 update it to the expected URL each time we fetch, in order to
1313 ensure we update properly if the location given in the project 1328 ensure we update properly if the location given in the project
1314 file changes. The origin remote is unused. *) 1329 file changes. The origin remote is unused. *)
1315 1330
1331 val git_program = "git"
1332
1316 fun git_command context libname args = 1333 fun git_command context libname args =
1317 FileBits.command context libname ("git" :: args) 1334 FileBits.command context libname (git_program :: args)
1318 1335
1319 fun git_command_output context libname args = 1336 fun git_command_output context libname args =
1320 FileBits.command_output context libname ("git" :: args) 1337 FileBits.command_output context libname (git_program :: args)
1338
1339 fun is_working context =
1340 case git_command_output context "" ["--version"] of
1341 OK "" => OK false
1342 | OK _ => OK true
1343 | ERROR e => ERROR e
1321 1344
1322 fun exists context libname = 1345 fun exists context libname =
1323 OK (OS.FileSys.isDir (FileBits.subpath context libname ".git")) 1346 OK (OS.FileSys.isDir (FileBits.subpath context libname ".git"))
1324 handle _ => OK false 1347 handle _ => OK false
1325 1348
1495 OK (FileBits.file_url (FileBits.libpath context libname)) 1518 OK (FileBits.file_url (FileBits.libpath context libname))
1496 1519
1497 end 1520 end
1498 1521
1499 (* SubXml - A parser for a subset of XML 1522 (* SubXml - A parser for a subset of XML
1500 https://bitbucket.org/cannam/sml-simplexml 1523 https://bitbucket.org/cannam/sml-subxml
1501 Copyright 2018 Chris Cannam. BSD licence. 1524 Copyright 2018 Chris Cannam. BSD licence.
1502 *) 1525 *)
1503 1526
1504 signature SUBXML = sig 1527 signature SUBXML = sig
1505 1528
1832 end 1855 end
1833 1856
1834 1857
1835 structure SvnControl :> VCS_CONTROL = struct 1858 structure SvnControl :> VCS_CONTROL = struct
1836 1859
1860 val svn_program = "svn"
1861
1837 fun svn_command context libname args = 1862 fun svn_command context libname args =
1838 FileBits.command context libname ("svn" :: args) 1863 FileBits.command context libname (svn_program :: args)
1839 1864
1840 fun svn_command_output context libname args = 1865 fun svn_command_output context libname args =
1841 FileBits.command_output context libname ("svn" :: args) 1866 FileBits.command_output context libname (svn_program :: args)
1842 1867
1843 fun svn_command_lines context libname args = 1868 fun svn_command_lines context libname args =
1844 case svn_command_output context libname args of 1869 case svn_command_output context libname args of
1845 ERROR e => ERROR e 1870 ERROR e => ERROR e
1846 | OK s => OK (String.tokens (fn c => c = #"\n" orelse c = #"\r") s) 1871 | OK s => OK (String.tokens (fn c => c = #"\n" orelse c = #"\r") s)
1853 case String.tokens (fn c => c = #":") line of 1878 case String.tokens (fn c => c = #":") line of
1854 [] => ("", "") 1879 [] => ("", "")
1855 | first::rest => 1880 | first::rest =>
1856 (first, strip_leading_ws (String.concatWith ":" rest)) 1881 (first, strip_leading_ws (String.concatWith ":" rest))
1857 end 1882 end
1883
1884 fun is_working context =
1885 case svn_command_output context "" ["--version"] of
1886 OK "" => OK false
1887 | OK _ => OK true
1888 | ERROR e => ERROR e
1858 1889
1859 structure X = SubXml 1890 structure X = SubXml
1860 1891
1861 fun svn_info context libname route = 1892 fun svn_info context libname route =
1862 (* SVN 1.9 has info --show-item which is just what we need, 1893 (* SVN 1.9 has info --show-item which is just what we need,
2011 fun update context (spec as { vcs, ... } : libspec) = 2042 fun update context (spec as { vcs, ... } : libspec) =
2012 (fn HG => H.update | GIT => G.update | SVN => S.update) vcs context spec 2043 (fn HG => H.update | GIT => G.update | SVN => S.update) vcs context spec
2013 2044
2014 fun id_of context (spec as { vcs, ... } : libspec) = 2045 fun id_of context (spec as { vcs, ... } : libspec) =
2015 (fn HG => H.id_of | GIT => G.id_of | SVN => S.id_of) vcs context spec 2046 (fn HG => H.id_of | GIT => G.id_of | SVN => S.id_of) vcs context spec
2047
2048 fun is_working context vcs =
2049 (fn HG => H.is_working | GIT => G.is_working | SVN => S.is_working)
2050 vcs context vcs
2016 2051
2017 end 2052 end
2018 2053
2019 2054
2020 type exclusions = string list 2055 type exclusions = string list
2415 "Notes" ^ "\n " ^ 2450 "Notes" ^ "\n " ^
2416 hline_to libname_width ^ "-+-" ^ 2451 hline_to libname_width ^ "-+-" ^
2417 hline_to libstate_width ^ "-+-" ^ 2452 hline_to libstate_width ^ "-+-" ^
2418 hline_to notes_width ^ "\n") 2453 hline_to notes_width ^ "\n")
2419 2454
2420 fun print_status with_network (libname, status) = 2455 fun print_status with_network (lib : libspec, status) =
2421 let val libstate_str = 2456 let val libstate_str =
2422 case status of 2457 case status of
2423 OK (ABSENT, _) => "Absent" 2458 OK (ABSENT, _) => "Absent"
2424 | OK (CORRECT, _) => if with_network then "Correct" else "Present" 2459 | OK (CORRECT, _) => if with_network then "Correct" else "Present"
2425 | OK (SUPERSEDED, _) => "Superseded" 2460 | OK (SUPERSEDED, _) => "Superseded"
2435 case status of 2470 case status of
2436 ERROR e => e 2471 ERROR e => e
2437 | _ => "" 2472 | _ => ""
2438 in 2473 in
2439 print (" " ^ 2474 print (" " ^
2440 pad_to libname_width libname ^ divider ^ 2475 pad_to libname_width (#libname lib) ^ divider ^
2441 pad_to libstate_width libstate_str ^ divider ^ 2476 pad_to libstate_width libstate_str ^ divider ^
2442 pad_to localstate_width localstate_str ^ divider ^ 2477 pad_to localstate_width localstate_str ^ divider ^
2443 error_str ^ "\n") 2478 error_str ^ "\n")
2444 end 2479 end
2445 2480
2446 fun print_update_outcome (libname, outcome) = 2481 fun print_update_outcome (lib : libspec, outcome) =
2447 let val outcome_str = 2482 let val outcome_str =
2448 case outcome of 2483 case outcome of
2449 OK id => "Ok" 2484 OK id => "Ok"
2450 | ERROR e => "Failed" 2485 | ERROR e => "Failed"
2451 val error_str = 2486 val error_str =
2452 case outcome of 2487 case outcome of
2453 ERROR e => e 2488 ERROR e => e
2454 | _ => "" 2489 | _ => ""
2455 in 2490 in
2456 print (" " ^ 2491 print (" " ^
2457 pad_to libname_width libname ^ divider ^ 2492 pad_to libname_width (#libname lib) ^ divider ^
2458 pad_to libstate_width outcome_str ^ divider ^ 2493 pad_to libstate_width outcome_str ^ divider ^
2459 error_str ^ "\n") 2494 error_str ^ "\n")
2460 end 2495 end
2461 2496
2462 fun act_and_print action print_header print_line (libs : libspec list) = 2497 fun vcs_name HG = ("Mercurial", "hg")
2463 let val lines = map (fn lib => (#libname lib, action lib)) libs 2498 | vcs_name GIT = ("Git", "git")
2499 | vcs_name SVN = ("Subversion", "svn")
2500
2501 fun print_problem_summary context lines =
2502 let val failed_vcs =
2503 foldl (fn (({ vcs, ... } : libspec, ERROR _), acc) => vcs::acc
2504 | (_, acc) => acc) [] lines
2505 fun report_nonworking vcs error =
2506 print ((if error = "" then "" else error ^ "\n\n") ^
2507 "Error: The project uses the " ^ (#1 (vcs_name vcs)) ^
2508 " version control system, but its\n" ^
2509 "executable program (" ^ (#2 (vcs_name vcs)) ^
2510 ") does not appear to be installed in the program path\n\n")
2511 fun check_working [] checked = ()
2512 | check_working (vcs::rest) checked =
2513 if List.exists (fn v => vcs = v) checked
2514 then check_working rest checked
2515 else
2516 case AnyLibControl.is_working context vcs of
2517 OK true => check_working rest checked
2518 | OK false => (report_nonworking vcs "";
2519 check_working rest (vcs::checked))
2520 | ERROR e => (report_nonworking vcs e;
2521 check_working rest (vcs::checked))
2522 in
2523 print "\nError: Some operations failed\n\n";
2524 check_working failed_vcs []
2525 end
2526
2527 fun act_and_print action print_header print_line context (libs : libspec list) =
2528 let val lines = map (fn lib => (lib, action lib)) libs
2529 val imperfect = List.exists (fn (_, ERROR _) => true | _ => false) lines
2464 val _ = print_header () 2530 val _ = print_header ()
2465 in 2531 in
2466 app print_line lines; 2532 app print_line lines;
2533 if imperfect then print_problem_summary context lines else ();
2467 lines 2534 lines
2468 end 2535 end
2469 2536
2470 fun return_code_for outcomes = 2537 fun return_code_for outcomes =
2471 foldl (fn ((_, result), acc) => 2538 foldl (fn ((_, result), acc) =>
2476 outcomes 2543 outcomes
2477 2544
2478 fun status_of_project ({ context, libs } : project) = 2545 fun status_of_project ({ context, libs } : project) =
2479 return_code_for (act_and_print (AnyLibControl.status context) 2546 return_code_for (act_and_print (AnyLibControl.status context)
2480 print_status_header (print_status false) 2547 print_status_header (print_status false)
2481 libs) 2548 context libs)
2482 2549
2483 fun review_project ({ context, libs } : project) = 2550 fun review_project ({ context, libs } : project) =
2484 return_code_for (act_and_print (AnyLibControl.review context) 2551 return_code_for (act_and_print (AnyLibControl.review context)
2485 print_status_header (print_status true) 2552 print_status_header (print_status true)
2486 libs) 2553 context libs)
2487 2554
2488 fun lock_project ({ context, libs } : project) = 2555 fun lock_project ({ context, libs } : project) =
2489 let val _ = if FileBits.verbose () 2556 let val _ = if FileBits.verbose ()
2490 then print ("Scanning IDs for lock file...\n") 2557 then print ("Scanning IDs for lock file...\n")
2491 else () 2558 else ()
2492 val outcomes = map (fn lib => 2559 val outcomes = map (fn lib => (lib, AnyLibControl.id_of context lib))
2493 (#libname lib, AnyLibControl.id_of context lib))
2494 libs 2560 libs
2495 val locks = 2561 val locks =
2496 List.concat 2562 List.concat
2497 (map (fn (libname, result) => 2563 (map (fn (lib : libspec, result) =>
2498 case result of 2564 case result of
2499 ERROR _ => [] 2565 ERROR _ => []
2500 | OK id => [{ libname = libname, id_or_tag = id }]) 2566 | OK id => [{ libname = #libname lib,
2567 id_or_tag = id }])
2501 outcomes) 2568 outcomes)
2502 val return_code = return_code_for outcomes 2569 val return_code = return_code_for outcomes
2503 val _ = print clear_line 2570 val _ = print clear_line
2504 in 2571 in
2505 if OS.Process.isSuccess return_code 2572 if OS.Process.isSuccess return_code
2509 end 2576 end
2510 2577
2511 fun update_project (project as { context, libs }) = 2578 fun update_project (project as { context, libs }) =
2512 let val outcomes = act_and_print 2579 let val outcomes = act_and_print
2513 (AnyLibControl.update context) 2580 (AnyLibControl.update context)
2514 print_outcome_header print_update_outcome libs 2581 print_outcome_header print_update_outcome
2582 context libs
2515 val _ = if List.exists (fn (_, OK _) => true | _ => false) outcomes 2583 val _ = if List.exists (fn (_, OK _) => true | _ => false) outcomes
2516 then lock_project project 2584 then lock_project project
2517 else OS.Process.success 2585 else OS.Process.success
2518 in 2586 in
2519 return_code_for outcomes 2587 return_code_for outcomes
2526 load_project userconfig rootpath pintype 2594 load_project userconfig rootpath pintype
2527 end 2595 end
2528 2596
2529 fun with_local_project pintype f = 2597 fun with_local_project pintype f =
2530 let val return_code = f (load_local_project pintype) 2598 let val return_code = f (load_local_project pintype)
2599 handle Fail msg => (print ("Error: " ^ msg);
2600 OS.Process.failure)
2531 handle e => (print ("Error: " ^ exnMessage e); 2601 handle e => (print ("Error: " ^ exnMessage e);
2532 OS.Process.failure) 2602 OS.Process.failure)
2533 val _ = print "\n"; 2603 val _ = print "\n";
2534 in 2604 in
2535 return_code 2605 return_code
2578 | ["install"] => install () 2648 | ["install"] => install ()
2579 | ["update"] => update () 2649 | ["update"] => update ()
2580 | ["lock"] => lock () 2650 | ["lock"] => lock ()
2581 | ["version"] => version () 2651 | ["version"] => version ()
2582 | "archive"::target::args => archive target args 2652 | "archive"::target::args => archive target args
2653 | arg::_ => (print ("Error: unknown argument \"" ^ arg ^ "\"\n");
2654 usage ())
2583 | _ => usage () 2655 | _ => usage ()
2584 in 2656 in
2585 OS.Process.exit return_code; 2657 OS.Process.exit return_code;
2586 () 2658 ()
2587 end 2659 end