comparison vext.sml @ 1746:bf4a7015033e

Update vext
author Chris Cannam
date Fri, 06 Oct 2017 13:28:35 +0100
parents 669bd699082d
children ffe59b457557
comparison
equal deleted inserted replaced
1745:3587df7758e7 1746:bf4a7015033e
1 (* This file is automatically generated from the individual 1 (*
2 source files in the Vext repository. *) 2 DO NOT EDIT THIS FILE.
3 This file is automatically generated from the individual
4 source files in the Vext repository.
5 *)
3 6
4 (* 7 (*
5 Vext 8 Vext
6 9
7 A simple manager for third-party source code dependencies 10 A simple manager for third-party source code dependencies
8 11
9 Copyright 2017 Chris Cannam. 12 Copyright 2017 Chris Cannam, Particular Programs Ltd,
13 and Queen Mary, University of London
10 14
11 Permission is hereby granted, free of charge, to any person 15 Permission is hereby granted, free of charge, to any person
12 obtaining a copy of this software and associated documentation 16 obtaining a copy of this software and associated documentation
13 files (the "Software"), to deal in the Software without 17 files (the "Software"), to deal in the Software without
14 restriction, including without limitation the rights to use, copy, 18 restriction, including without limitation the rights to use, copy,
25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 29 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
26 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 30 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
27 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 33
30 Except as contained in this notice, the names of Chris Cannam and 34 Except as contained in this notice, the names of Chris Cannam,
31 Particular Programs Ltd shall not be used in advertising or 35 Particular Programs Ltd, and Queen Mary, University of London
32 otherwise to promote the sale, use or other dealings in this 36 shall not be used in advertising or otherwise to promote the sale,
33 Software without prior written authorization. 37 use or other dealings in this Software without prior written
38 authorization.
34 *) 39 *)
35 40
36 val vext_version = "0.9.6" 41 val vext_version = "0.9.8"
37 42
38 43
39 datatype vcs = 44 datatype vcs =
40 HG | 45 HG |
41 GIT 46 GIT
129 134
130 structure VextFilenames = struct 135 structure VextFilenames = struct
131 val project_file = "vext-project.json" 136 val project_file = "vext-project.json"
132 val project_lock_file = "vext-lock.json" 137 val project_lock_file = "vext-lock.json"
133 val user_config_file = ".vext.json" 138 val user_config_file = ".vext.json"
139 val archive_dir = ".vext-archive"
134 end 140 end
135 141
136 signature VCS_CONTROL = sig 142 signature VCS_CONTROL = sig
137 143
138 (** Test whether the library is present locally at all *) 144 (** Test whether the library is present locally at all *)
190 val command : context -> libname -> string list -> unit result 196 val command : context -> libname -> string list -> unit result
191 val file_contents : string -> string 197 val file_contents : string -> string
192 val mydir : unit -> string 198 val mydir : unit -> string
193 val homedir : unit -> string 199 val homedir : unit -> string
194 val mkpath : string -> unit result 200 val mkpath : string -> unit result
201 val rmpath : string -> unit result
195 val project_spec_path : string -> string 202 val project_spec_path : string -> string
196 val project_lock_path : string -> string 203 val project_lock_path : string -> string
197 val verbose : unit -> bool 204 val verbose : unit -> bool
198 end = struct 205 end = struct
199 206
374 arcs = rev (tl (rev arcs)) }) of 381 arcs = rev (tl (rev arcs)) }) of
375 ERROR e => ERROR e 382 ERROR e => ERROR e
376 | OK () => ((OS.FileSys.mkDir path; OK ()) 383 | OK () => ((OS.FileSys.mkDir path; OK ())
377 handle OS.SysErr (e, _) => 384 handle OS.SysErr (e, _) =>
378 ERROR ("Directory creation failed: " ^ e)) 385 ERROR ("Directory creation failed: " ^ e))
386
387 fun rmpath path =
388 let open OS
389 fun files_from dirstream =
390 case FileSys.readDir dirstream of
391 NONE => []
392 | SOME file =>
393 (* readDir is supposed to filter these,
394 but let's be extra cautious: *)
395 if file = Path.parentArc orelse file = Path.currentArc
396 then files_from dirstream
397 else file :: files_from dirstream
398 fun contents dir =
399 let val stream = FileSys.openDir dir
400 val files = map (fn f => Path.joinDirFile
401 { dir = dir, file = f })
402 (files_from stream)
403 val _ = FileSys.closeDir stream
404 in files
405 end
406 fun remove path =
407 if FileSys.isLink path (* dangling links bother isDir *)
408 then FileSys.remove path
409 else if FileSys.isDir path
410 then (app remove (contents path); FileSys.rmDir path)
411 else FileSys.remove path
412 in
413 (remove path; OK ())
414 handle SysErr (e, _) => ERROR ("Path removal failed: " ^ e)
415 end
379 end 416 end
380 417
381 functor LibControlFn (V: VCS_CONTROL) :> LIB_CONTROL = struct 418 functor LibControlFn (V: VCS_CONTROL) :> LIB_CONTROL = struct
382 419
383 (* Valid states for unpinned libraries: 420 (* Valid states for unpinned libraries:
1396 1433
1397 fun id_of context (spec as { vcs, ... } : libspec) = 1434 fun id_of context (spec as { vcs, ... } : libspec) =
1398 (fn HG => H.id_of | GIT => G.id_of) vcs context spec 1435 (fn HG => H.id_of | GIT => G.id_of) vcs context spec
1399 end 1436 end
1400 1437
1438
1439 type exclusions = string list
1440
1441 structure Archive :> sig
1442
1443 val archive : string * exclusions -> project -> OS.Process.status
1444
1445 end = struct
1446
1447 (* The idea of "archive" is to replace hg/git archive, which won't
1448 include files, like the Vext-introduced external libraries,
1449 that are not under version control with the main repo.
1450
1451 The process goes like this:
1452
1453 - Make sure we have a target filename from the user, and take
1454 its basename as our archive directory name
1455
1456 - Make an "archive root" subdir of the project repo, named
1457 typically .vext-archive
1458
1459 - Identify the VCS used for the project repo. Note that any
1460 explicit references to VCS type in this structure are to
1461 the VCS used for the project (something Vext doesn't
1462 otherwise care about), not for an individual library
1463
1464 - Synthesise a Vext project with the archive root as its
1465 root path, "." as its extdir, with one library whose
1466 name is the user-supplied basename and whose explicit
1467 source URL is the original project root; update that
1468 project -- thus cloning the original project to a subdir
1469 of the archive root
1470
1471 - Synthesise a Vext project identical to the original one for
1472 this project, but with the newly-cloned copy as its root
1473 path; update that project -- thus checking out clean copies
1474 of the external library dirs
1475
1476 - Call out to an archive program to archive up the new copy,
1477 running e.g.
1478 tar cvzf project-release.tar.gz \
1479 --exclude=.hg --exclude=.git project-release
1480 in the archive root dir
1481
1482 - (We also omit the vext-project.json file and any trace of
1483 Vext. It can't properly be run in a directory where the
1484 external project folders already exist but their repo history
1485 does not. End users shouldn't get to see Vext)
1486
1487 - Clean up by deleting the new copy
1488 *)
1489
1490 fun project_vcs_and_id dir =
1491 let val context = {
1492 rootpath = dir,
1493 extdir = ".",
1494 providers = [],
1495 accounts = []
1496 }
1497 val vcs_maybe =
1498 case [HgControl.exists context ".",
1499 GitControl.exists context "."] of
1500 [OK true, OK false] => OK HG
1501 | [OK false, OK true] => OK GIT
1502 | _ => ERROR ("Unable to identify VCS for directory " ^ dir)
1503 in
1504 case vcs_maybe of
1505 ERROR e => ERROR e
1506 | OK vcs =>
1507 case (fn HG => HgControl.id_of | GIT => GitControl.id_of)
1508 vcs context "." of
1509 ERROR e => ERROR ("Unable to obtain id of project repo: "
1510 ^ e)
1511 | OK id => OK (vcs, id)
1512 end
1513
1514 fun make_archive_root (context : context) =
1515 let val path = OS.Path.joinDirFile {
1516 dir = #rootpath context,
1517 file = VextFilenames.archive_dir
1518 }
1519 in
1520 case FileBits.mkpath path of
1521 ERROR e => raise Fail ("Failed to create archive directory \""
1522 ^ path ^ "\": " ^ e)
1523 | OK () => path
1524 end
1525
1526 fun archive_path archive_dir target_name =
1527 OS.Path.joinDirFile {
1528 dir = archive_dir,
1529 file = target_name
1530 }
1531
1532 fun check_nonexistent path =
1533 case SOME (OS.FileSys.fileSize path) handle OS.SysErr _ => NONE of
1534 NONE => ()
1535 | _ => raise Fail ("Path " ^ path ^ " exists, not overwriting")
1536
1537 fun file_url path =
1538 let val forward_path =
1539 String.translate (fn #"\\" => "/" |
1540 c => Char.toString c) path
1541 in
1542 (* Path is expected to be absolute already, but if it
1543 starts with a drive letter, we'll need an extra slash *)
1544 case explode forward_path of
1545 #"/"::rest => "file:///" ^ implode rest
1546 | _ => "file:///" ^ forward_path
1547 end
1548
1549 fun make_archive_copy target_name (vcs, project_id)
1550 ({ context, ... } : project) =
1551 let val archive_root = make_archive_root context
1552 val synthetic_context = {
1553 rootpath = archive_root,
1554 extdir = ".",
1555 providers = [],
1556 accounts = []
1557 }
1558 val synthetic_library = {
1559 libname = target_name,
1560 vcs = vcs,
1561 source = URL_SOURCE (file_url (#rootpath context)),
1562 branch = DEFAULT_BRANCH, (* overridden by pinned id below *)
1563 project_pin = PINNED project_id,
1564 lock_pin = PINNED project_id
1565 }
1566 val path = archive_path archive_root target_name
1567 val _ = print ("Cloning original project to " ^ path
1568 ^ " at revision " ^ project_id ^ "...\n");
1569 val _ = check_nonexistent path
1570 in
1571 case AnyLibControl.update synthetic_context synthetic_library of
1572 ERROR e => ERROR ("Failed to clone original project to "
1573 ^ path ^ ": " ^ e)
1574 | OK _ => OK archive_root
1575 end
1576
1577 fun update_archive archive_root target_name
1578 (project as { context, ... } : project) =
1579 let val synthetic_context = {
1580 rootpath = archive_path archive_root target_name,
1581 extdir = #extdir context,
1582 providers = #providers context,
1583 accounts = #accounts context
1584 }
1585 in
1586 foldl (fn (lib, acc) =>
1587 case acc of
1588 ERROR e => ERROR e
1589 | OK _ => AnyLibControl.update synthetic_context lib)
1590 (OK "")
1591 (#libs project)
1592 end
1593
1594 datatype packer = TAR
1595 | TAR_GZ
1596 | TAR_BZ2
1597 | TAR_XZ
1598 (* could add other packers, e.g. zip, if we knew how to
1599 handle the file omissions etc properly in pack_archive *)
1600
1601 fun packer_and_basename path =
1602 let val extensions = [ (".tar", TAR),
1603 (".tar.gz", TAR_GZ),
1604 (".tar.bz2", TAR_BZ2),
1605 (".tar.xz", TAR_XZ)]
1606 val filename = OS.Path.file path
1607 in
1608 foldl (fn ((ext, packer), acc) =>
1609 if String.isSuffix ext filename
1610 then SOME (packer,
1611 String.substring (filename, 0,
1612 String.size filename -
1613 String.size ext))
1614 else acc)
1615 NONE
1616 extensions
1617 end
1618
1619 fun pack_archive archive_root target_name target_path packer exclusions =
1620 case FileBits.command {
1621 rootpath = archive_root,
1622 extdir = ".",
1623 providers = [],
1624 accounts = []
1625 } "" ([
1626 "tar",
1627 case packer of
1628 TAR => "cf"
1629 | TAR_GZ => "czf"
1630 | TAR_BZ2 => "cjf"
1631 | TAR_XZ => "cJf",
1632 target_path,
1633 "--exclude=.hg",
1634 "--exclude=.git",
1635 "--exclude=vext",
1636 "--exclude=vext.sml",
1637 "--exclude=vext.ps1",
1638 "--exclude=vext.bat",
1639 "--exclude=vext-project.json",
1640 "--exclude=vext-lock.json"
1641 ] @ (map (fn e => "--exclude=" ^ e) exclusions) @
1642 [ target_name ])
1643 of
1644 ERROR e => ERROR e
1645 | OK _ => FileBits.rmpath (archive_path archive_root target_name)
1646
1647 fun archive (target_path, exclusions) (project : project) =
1648 let val _ = check_nonexistent target_path
1649 val (packer, name) =
1650 case packer_and_basename target_path of
1651 NONE => raise Fail ("Unsupported archive file extension in "
1652 ^ target_path)
1653 | SOME pn => pn
1654 val details =
1655 case project_vcs_and_id (#rootpath (#context project)) of
1656 ERROR e => raise Fail e
1657 | OK details => details
1658 val archive_root =
1659 case make_archive_copy name details project of
1660 ERROR e => raise Fail e
1661 | OK archive_root => archive_root
1662 val outcome =
1663 case update_archive archive_root name project of
1664 ERROR e => ERROR e
1665 | OK _ =>
1666 case pack_archive archive_root name
1667 target_path packer exclusions of
1668 ERROR e => ERROR e
1669 | OK _ => OK ()
1670 in
1671 case outcome of
1672 ERROR e => raise Fail e
1673 | OK () => OS.Process.success
1674 end
1675
1676 end
1677
1401 val libobjname = "libraries" 1678 val libobjname = "libraries"
1402 1679
1403 fun load_libspec spec_json lock_json libname : libspec = 1680 fun load_libspec spec_json lock_json libname : libspec =
1404 let open JsonBits 1681 let open JsonBits
1405 val libobj = lookup_mandatory spec_json [libobjname, libname] 1682 val libobj = lookup_mandatory spec_json [libobjname, libname]
1658 if OS.Process.isSuccess return_code 1935 if OS.Process.isSuccess return_code
1659 then save_lock_file (#rootpath context) locks 1936 then save_lock_file (#rootpath context) locks
1660 else (); 1937 else ();
1661 return_code 1938 return_code
1662 end 1939 end
1663 1940
1664 fun load_local_project pintype = 1941 fun load_local_project pintype =
1665 let val userconfig = load_userconfig () 1942 let val userconfig = load_userconfig ()
1666 val rootpath = OS.FileSys.getDir () 1943 val rootpath = OS.FileSys.getDir ()
1667 in 1944 in
1668 load_project userconfig rootpath pintype 1945 load_project userconfig rootpath pintype
1669 end 1946 end
1670 1947
1671 fun with_local_project pintype f = 1948 fun with_local_project pintype f =
1672 let val return_code = f (load_local_project pintype) 1949 let val return_code = f (load_local_project pintype)
1673 handle e => 1950 handle e => (print ("Error: " ^ exnMessage e);
1674 (print ("Failed with exception: " ^ 1951 OS.Process.failure)
1675 (exnMessage e) ^ "\n");
1676 OS.Process.failure)
1677 val _ = print "\n"; 1952 val _ = print "\n";
1678 in 1953 in
1679 return_code 1954 return_code
1680 end 1955 end
1681 1956
1699 ^ " status print quick report on local status only, without using network\n" 1974 ^ " status print quick report on local status only, without using network\n"
1700 ^ " review check configured libraries against their providers, and report\n" 1975 ^ " review check configured libraries against their providers, and report\n"
1701 ^ " install update configured libraries according to project specs and lock file\n" 1976 ^ " install update configured libraries according to project specs and lock file\n"
1702 ^ " update update configured libraries and lock file according to project specs\n" 1977 ^ " update update configured libraries and lock file according to project specs\n"
1703 ^ " lock update lock file to match local library status\n" 1978 ^ " lock update lock file to match local library status\n"
1979 ^ " archive pack up project and all libraries into an archive file\n"
1980 ^ " (invoke as 'vext archive target-file.tar.gz')\n"
1704 ^ " version print the Vext version number and exit\n\n"); 1981 ^ " version print the Vext version number and exit\n\n");
1705 OS.Process.failure) 1982 OS.Process.failure)
1983
1984 fun archive target args =
1985 case args of
1986 [] =>
1987 with_local_project USE_LOCKFILE (Archive.archive (target, []))
1988 | "--exclude"::xs =>
1989 with_local_project USE_LOCKFILE (Archive.archive (target, xs))
1990 | _ => usage ()
1706 1991
1707 fun vext args = 1992 fun vext args =
1708 let val return_code = 1993 let val return_code =
1709 case args of 1994 case args of
1710 ["review"] => review () 1995 ["review"] => review ()
1711 | ["status"] => status () 1996 | ["status"] => status ()
1712 | ["install"] => install () 1997 | ["install"] => install ()
1713 | ["update"] => update () 1998 | ["update"] => update ()
1714 | ["lock"] => lock () 1999 | ["lock"] => lock ()
1715 | ["version"] => version () 2000 | ["version"] => version ()
2001 | "archive"::target::args => archive target args
1716 | _ => usage () 2002 | _ => usage ()
1717 in 2003 in
1718 OS.Process.exit return_code; 2004 OS.Process.exit return_code;
1719 () 2005 ()
1720 end 2006 end