Revision 124:fc093b176444 vext.sml

View differences:

vext.sml
38 38
    authorization.
39 39
*)
40 40

  
41
val vext_version = "0.9.6"
41
val vext_version = "0.9.8"
42 42

  
43 43

  
44 44
datatype vcs =
......
136 136
    val project_file = "vext-project.json"
137 137
    val project_lock_file = "vext-lock.json"
138 138
    val user_config_file = ".vext.json"
139
    val archive_dir = ".vext-archive"
139 140
end
140 141
                   
141 142
signature VCS_CONTROL = sig
......
197 198
    val mydir : unit -> string
198 199
    val homedir : unit -> string
199 200
    val mkpath : string -> unit result
201
    val rmpath : string -> unit result
200 202
    val project_spec_path : string -> string
201 203
    val project_lock_path : string -> string
202 204
    val verbose : unit -> bool
......
381 383
                   | OK () => ((OS.FileSys.mkDir path; OK ())
382 384
                               handle OS.SysErr (e, _) =>
383 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
384 416
end
385 417
                                         
386 418
functor LibControlFn (V: VCS_CONTROL) :> LIB_CONTROL = struct
......
1403 1435
        (fn HG => H.id_of | GIT => G.id_of) vcs context spec
1404 1436
end
1405 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

  
1406 1678
val libobjname = "libraries"
1407 1679
                                             
1408 1680
fun load_libspec spec_json lock_json libname : libspec =
......
1665 1937
        else ();
1666 1938
        return_code
1667 1939
    end
1668
        
1940
    
1669 1941
fun load_local_project pintype =
1670 1942
    let val userconfig = load_userconfig ()
1671 1943
        val rootpath = OS.FileSys.getDir ()
......
1675 1947

  
1676 1948
fun with_local_project pintype f =
1677 1949
    let val return_code = f (load_local_project pintype)
1678
                          handle e =>
1679
                                 (print ("Failed with exception: " ^
1680
                                         (exnMessage e) ^ "\n");
1681
                                  OS.Process.failure)
1950
                          handle e => (print ("Error: " ^ exnMessage e);
1951
                                       OS.Process.failure)
1682 1952
        val _ = print "\n";
1683 1953
    in
1684 1954
        return_code
......
1706 1976
            ^ "  install  update configured libraries according to project specs and lock file\n"
1707 1977
            ^ "  update   update configured libraries and lock file according to project specs\n"
1708 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"
1709 1981
            ^ "  version  print the Vext version number and exit\n\n");
1710 1982
    OS.Process.failure)
1711 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 ()
1991

  
1712 1992
fun vext args =
1713 1993
    let val return_code = 
1714 1994
            case args of
......
1718 1998
              | ["update"] => update ()
1719 1999
              | ["lock"] => lock ()
1720 2000
              | ["version"] => version ()
2001
              | "archive"::target::args => archive target args
1721 2002
              | _ => usage ()
1722 2003
    in
1723 2004
        OS.Process.exit return_code;

Also available in: Unified diff