//
// Copyright (c) 2011, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 22 Jun 11 Brian Frank Creation
//
**
** InstallCmd installs a pod from the repo to the local environment
** and may install/upgrade other pods as part of the dependency chain.
**
internal class InstallCmd : Command
{
//////////////////////////////////////////////////////////////////////////
// Usage
//////////////////////////////////////////////////////////////////////////
override Str name() { "install" }
override Str summary() { "install a pod from repo to local env" }
//////////////////////////////////////////////////////////////////////////
// Args/Opts
//////////////////////////////////////////////////////////////////////////
@CommandArg
{
name = "query"
help = "query filter for pods to install"
}
Str? query
//////////////////////////////////////////////////////////////////////////
// Execution
//////////////////////////////////////////////////////////////////////////
override Void run()
{
// perform query for pods to install
specs := repo.query(query, 1)
// if no matches we are done
if (specs.isEmpty)
{
out.printLine("No install pods matched")
return
}
// convert specs to install items
specs.each |spec|
{
items[spec.name] = InstallItem(spec, env.find(spec.name))
}
// calculate dependencies and add to install items
findDepends
// print game plan and confirm
printInstallPlan(items)
if (!confirm("Install?")) return
// create temp dir for staging
ts := DateTime.now.toLocale("YYMMDD-hhmmss")
rand := Buf.random(4).toHex
stageDir := Env.cur.tempDir + `fanr-stage-${ts}-${rand}/`
// download each pod to the staging dir
out.printLine
items.each |item| { download(item, stageDir) }
out.printLine
out.printLine("Download successful ($items.size pods)")
// now that we have safely downloaded everything,
// do the actual install to local environment
out.printLine
items.each |item| { install(item, stageDir) }
out.printLine
out.printLine("Installation successful ($items.size pods)")
}
private Void findDepends()
{
// recursively check dependencies until we have them all checked
while (true)
{
again := false
items.dup.each |item|
{
if (item.dependsChecked) return
checkDepends(item)
item.dependsChecked = true
again = true
}
if (!again) break
}
}
private Void checkDepends(InstallItem item)
{
item.spec.depends.each |d|
{
// check if we meet depend with locally installed pod
curInstalled := env.find(d.name)
if (curInstalled != null && d.match(curInstalled.version)) return
// check if we meet depend on pod already in our install list
toInstall := items[d.name]
if (toInstall != null && d.match(toInstall.spec.version)) return
// query the repo, eventually it would more optimized to
// batch as many dependency queries as possible together
newInstall := repo.query(d.toStr, 1).first
if (newInstall != null)
{
items[newInstall.name] = InstallItem(newInstall, curInstalled)
return
}
// give up
throw err("Cannot meet dependency '$d' for '$item.name'")
}
}
private Void printInstallPlan(Str:InstallItem items)
{
maxName := (items.vals.max |a, b| { a.name.size <=> b.name.size }).name.size
maxAction := (items.vals.max |a, b| { a.actionStr.size <=> b.actionStr.size }).actionStr.size
maxOldVer := (items.vals.max |a, b| { a.oldVerStr.size <=> b.oldVerStr.size }).oldVerStr.size
out.printLine
items.keys.sort.each |name|
{
item := items[name]
out.printLine(name.justl(maxName) + " [" +
item.actionStr.justl(maxAction) + "] " +
item.oldVerStr.justl(maxOldVer) + " => " +
item.newVerStr)
}
out.printLine
}
private Void download(InstallItem item, File stageDir)
{
out.print("Downloading ${item.name} ... ").flush
dest := stageDir + `${item.name}.pod`
destOut := dest.out
try
{
repo.read(item.spec).pipe(destOut)
destOut.close
}
catch (Err e)
{
out.printLine.printLine
destOut.close
throw e
}
out.printLine("Complete")
}
private Void install(InstallItem item, File stageDir)
{
out.print("Installing ${item.name} ... ").flush
src := stageDir + `${item.name}.pod`
dest := Env.cur.workDir + `lib/fan/`
src.copyInto(dest, ["overwrite":true])
out.printLine("Complete")
}
Str:InstallItem items := [:] // items to install
}
**************************************************************************
** InstallItem
**************************************************************************
internal class InstallItem
{
new make(PodSpec spec, PodSpec? cur)
{
this.spec = spec
this.cur = cur
this.oldVerStr = cur == null ? "not-installed" : cur.version.toStr
this.newVerStr = spec.version.toStr
}
Str name() { spec.name }
Str actionStr()
{
if (isSkip) return "skip"
if (cur == null) return "install"
if (cur.version > spec.version) return "downgrade"
return "upgrade"
}
PodSpec spec // what is being installed from repo
PodSpec? cur // current version installed or null
Str oldVerStr // not-installed or current version
Str newVerStr // up-to-date or version to install
Bool dependsChecked // have we checked depends on this guy
** If cur exactly matches spec
Bool isSkip() { spec.version == cur?.version }
}