From 07ac227e279cce100ae99650f4231d5da6464961 Mon Sep 17 00:00:00 2001 From: Alexey Lunacharsky Date: Sun, 5 May 2013 13:50:44 +0200 Subject: [PATCH] Workaround --clean-boot issue on widows --- src/main/scala/clean.scala | 111 ++++++++++++++++++++++++++++--- src/main/scala/conscript.scala | 2 +- src/main/scala/credentials.scala | 10 +-- 3 files changed, 110 insertions(+), 13 deletions(-) mode change 100644 => 100755 src/main/scala/clean.scala diff --git a/src/main/scala/clean.scala b/src/main/scala/clean.scala old mode 100644 new mode 100755 index 130d028..3a0685f --- a/src/main/scala/clean.scala +++ b/src/main/scala/clean.scala @@ -1,16 +1,111 @@ package conscript -object Clean { - def clean(files: Array[java.io.File]): Option[String] = - (Option.empty[String] /: files) { (a, e) => - a orElse { - if (e.isDirectory) - clean(e.listFiles).orElse(delete(e)) - else delete(e) +import java.io.{PrintWriter, File} +import java.text.SimpleDateFormat +import java.util.Date +import scala.util.control.Exception._ +import scala.util.Random +import java.util.regex.Pattern +import scala.util.matching.Regex + +object Clean extends OsDetect { + + /** + * Windows java runtime can't success with File.delete() while cleaning SBT boot directory. + * This helper object adds workaround of the issue by the using cmd scripts and windows task scheduler. + */ + private object Windows { + lazy val timeFormat = new SimpleDateFormat("HH:mm:ss") + + def exec(args: String*) = allCatch.either { sys.runtime.exec(args.toArray) } + + // TODO: this could be rid off when will switch scala 2.10 by the using of string substitution + def resolvePlaceholders(text:String)(implicit placeholders: Map[String, Any]) = + (text /: placeholders) { + case (text, (k, v)) => text.replaceAll( + Pattern.quote("${" + k + "}"), + Regex.quoteReplacement(v.toString) + ) + } + + def writeScript(f:File)(text:String)(implicit placeholders: Map[String, Any]) = { + var printer = Option.empty[PrintWriter] + allCatch.andFinally(printer.map(_.close())).either { + printer = Some(new PrintWriter(f, "UTF-8")) + resolvePlaceholders(text).split("\\n").map(printer.get.println) + } + } + + def scheduleClean(file:File) = { + def time = timeFormat.format(new Date) + val taskName = "ConscriptClean" + Random.nextInt() + val cleanScript = File.createTempFile("conscript-clean-boot", ".bat") + val scheduleScript = File.createTempFile("conscript-schedule-clean", ".bat") + val createFlags = if (isXP) "/ru SYSTEM " else "/f" // win XP uses different version of schtasks + + implicit val scriptPlaceholders = Map( + "createFlags" -> createFlags, + "taskName" -> taskName, + "scheduleScript" -> scheduleScript, + "cleanScript" -> cleanScript, + "time" -> time, + "file" -> file + ) + + for { + _ <- writeScript(scheduleScript) { + """ + |@echo off + |schtasks /Create ${createFlags} /tn ${taskName} /sc ONCE /tr "${cleanScript}" /st ${time} + |schtasks /Run /tn ${taskName} + """.stripMargin + } .right + _ <- writeScript(cleanScript) { + """ + |@echo off + |schtasks /Delete /f /tn ${taskName} + |rmdir /q /s "${file}" + |dir "${file}" && ( + | "${scheduleScript}" + |) || ( + | del /q "${cleanScript}" + | del /q "${scheduleScript}" + |) + """.stripMargin + } .right + } yield { + sys.addShutdownHook { + exec(scheduleScript.getAbsolutePath) + } + "Clean script scheduled successfully" } } + } + + /** + * Doing recursive removing of the file (or dir). + * + * On windows SBT launcher boot directory cannot be cleaned while launcher is running so we do schedule + * this procedure with the system task manager to run from parallel service process + * + * @param file a file to cleanRec + * @return some error message or none if success + */ + def clean(file:File):Option[String] = + cleanRec(file).flatMap { error => + windows.map(_ => Windows.scheduleClean(file).left.toOption.map(_.getMessage)) getOrElse(Some(error)) + } + + private def cleanRec(file:File):Option[String] = + if (file.isDirectory) + (Option.empty[String] /: file.listFiles) { (a, f) => + a orElse cleanRec(f) + } .orElse(delete(file)) + else delete(file) - private def delete(file: java.io.File) = + private def delete(file: File) = if (file.delete()) None else Some("Unable to delete %s".format(file)) + } + diff --git a/src/main/scala/conscript.scala b/src/main/scala/conscript.scala index 7431cdf..070c2f9 100644 --- a/src/main/scala/conscript.scala +++ b/src/main/scala/conscript.scala @@ -73,7 +73,7 @@ object Conscript { parsed.map { case c if c.clean_boot => if (Apply.bootdir.exists && Apply.bootdir.isDirectory) - Clean.clean(Apply.bootdir.listFiles).toLeft("Cleaned boot directory (%s)".format(Apply.bootdir)) + Clean.clean(Apply.bootdir).toLeft("Cleaned boot directory (%s)".format(Apply.bootdir)) else Left("No boot directory found at " + Apply.bootdir) case c if c.usage => Right(parser.usage) diff --git a/src/main/scala/credentials.scala b/src/main/scala/credentials.scala index eb7cf21..cf42195 100644 --- a/src/main/scala/credentials.scala +++ b/src/main/scala/credentials.scala @@ -1,11 +1,8 @@ package conscript -import dispatch._ import com.ning.http.client.RequestBuilder -trait Credentials { - import scala.util.control.Exception.allCatch - +trait Credentials extends OsDetect { def withCredentials(req: RequestBuilder) = (oauth map { case token => req.addHeader("Authorization", "token %s".format(token)) @@ -14,9 +11,14 @@ trait Credentials { def oauth: Option[String] = Config.get("gh.access") +} + +trait OsDetect { def windows = System.getProperty("os.name") match { case x: String if x contains "Windows" => Some(x) case _ => None } + + def isXP = windows.map(_.contains("XP")).getOrElse(false) }