Advertisement
Guest User

Untitled

a guest
Aug 28th, 2015
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 21.06 KB | None | 0 0
  1. package android
  2.  
  3. import java.io.{File, _}
  4. import java.util.concurrent.TimeUnit
  5.  
  6. import android.Keys._
  7. import com.android.builder.model.{JavaLibrary, AndroidLibrary, MavenCoordinates}
  8. import com.hanhuy.gradle.discovery.GradleBuildModel
  9. import org.gradle.tooling.internal.consumer.DefaultGradleConnector
  10. import org.gradle.tooling.{GradleConnector, ProjectConnection}
  11. import sbt.Keys._
  12. import sbt._
  13.  
  14. import scala.annotation.tailrec
  15. import scala.collection.JavaConverters._
  16. import scala.language.postfixOps
  17. import scala.util.Try
  18.  
  19. /**
  20. * @author pfnguyen
  21. */
  22. trait GradleBuild extends Build {
  23. private[this] var projectsMap = Map.empty[String,Project]
  24.  
  25. def gradle = projectsMap.apply _
  26.  
  27. def importFromGradle(): Unit = {
  28. }
  29.  
  30. importFromGradle()
  31.  
  32. override def projects: Seq[Project] = {
  33. val start = System.currentTimeMillis
  34. val originalProjects = super.projects.map (p => (p.id, p)).toMap
  35. val initgradle = IO.readLinesURL(Tasks.resourceUrl("plugin-init.gradle"))
  36. val f = File.createTempFile("plugin-init", ".gradle")
  37. IO.writeLines(f, initgradle)
  38. f.deleteOnExit()
  39.  
  40. println("Searching for android gradle projects...")
  41. val gconnection = GradleConnector.newConnector.asInstanceOf[DefaultGradleConnector]
  42. gconnection.daemonMaxIdleTime(60, TimeUnit.SECONDS)
  43. gconnection.setVerboseLogging(false)
  44.  
  45. try {
  46. val discovered = GradleBuildSerializer.toposort(processDirectoryAt(file("."), f, gconnection)._2)
  47. f.delete()
  48. val projects = discovered map { sp =>
  49. val deps = sp.dependencies
  50. val p = sp.project
  51. val orig = originalProjects.get(p.id)
  52.  
  53. val ds = deps.toList.map (dep => discovered.find(_.id == dep).get.project)
  54. val p2 = p.settings(Plugin.buildWith(ds: _*): _*).dependsOn(ds map { d =>
  55. classpathDependency(Project.projectToRef(d)) }: _*)
  56. orig.fold(p2)(o => p2.copy(settings = o.asInstanceOf[ProjectDefinition[_]].settings ++ p.settings))
  57. }
  58.  
  59. projectsMap = projects map { p => (p.id, p) } toMap
  60.  
  61. val end = System.currentTimeMillis
  62. val elapsed = (end - start) / 1000.0f
  63. println(f"Discovered gradle projects (in $elapsed%.02fs):")
  64. println(projects.map(p => f"${p.id}%20s at ${p.base}").mkString("\n"))
  65. discovered foreach { d => println(d.serialized) }
  66. projects
  67. } catch {
  68. case ex: Exception =>
  69. @tailrec
  70. def collectMessages(e: Throwable, msgs: List[String] = Nil, seen: Set[Throwable] = Set.empty): String = {
  71. if (e == null || seen(e))
  72. msgs.mkString("\n")
  73. else
  74. collectMessages(e.getCause, e.getMessage :: msgs, seen + e)
  75. }
  76. throw new MessageOnlyException(collectMessages(ex))
  77. }
  78. }
  79.  
  80. val nullsink = new OutputStream {
  81. override def write(b: Int) = ()
  82. }
  83.  
  84. def modelBuilder[A](c: ProjectConnection, model: Class[A]) = {
  85. c.model(model)
  86. .setStandardOutput(nullsink)
  87. .setStandardError(nullsink)
  88. }
  89. def initScriptModelBuilder[A](c: ProjectConnection, model: Class[A], initscript: File) =
  90. modelBuilder(c, model).withArguments(
  91. "--init-script", initscript.getAbsolutePath)
  92.  
  93. def gradleBuildModel(c: ProjectConnection, initscript: File) =
  94. initScriptModelBuilder(c, classOf[GradleBuildModel], initscript).get()
  95.  
  96. import GradleBuildSerializer._
  97. def processDirectoryAt(base: File, initscript: File,
  98. connector: GradleConnector,
  99. repositories: List[Resolver] = Nil, seen: Set[File] = Set.empty): (Set[File],List[SbtProject]) = {//(Project,Set[String])]) = {
  100. val c = connector.forProjectDirectory(base).connect()
  101. val model = gradleBuildModel(c, initscript)
  102. val prj = model.getGradleProject
  103. val discovery = model.getDiscovery
  104. val repos = repositories ++ (
  105. model.getRepositories.getResolvers.asScala.toList map (r =>
  106. r.getUrl.toString at r.getUrl.toString))
  107.  
  108. val (visited,subprojects) = prj.getChildren.asScala.toList.foldLeft((seen + base.getCanonicalFile,List.empty[SbtProject])) { case ((saw,acc),child) =>
  109. // gradle 2.4 added getProjectDirectory
  110. val childDir = Try(child.getProjectDirectory).getOrElse(file(child.getPath.replace(":", ""))).getCanonicalFile
  111. if (!saw(childDir)) {
  112. println("Processing gradle sub-project at: " + childDir.getName)
  113. val (visited, subs) = processDirectoryAt(childDir, initscript, connector, repos, saw + childDir)
  114. (visited ++ saw, subs ++ acc)
  115. } else
  116. (saw,acc)
  117. }
  118.  
  119. try {
  120. if (discovery.isApplication || discovery.isLibrary) {
  121. val ap = model.getAndroidProject
  122. val sourceVersion = ap.getJavaCompileOptions.getSourceCompatibility
  123. val targetVersion = ap.getJavaCompileOptions.getTargetCompatibility
  124.  
  125. val default = ap.getDefaultConfig
  126. val flavor = default.getProductFlavor
  127. val sourceProvider = default.getSourceProvider
  128.  
  129. val optional: List[SbtSetting] = Option(flavor.getApplicationId).toList.map {
  130. applicationId in Android /:= _
  131. } ++ Option(flavor.getVersionCode).toList.map {
  132. versionCode in Android /:= Some(_)
  133. } ++ Option(flavor.getVersionName).toList.map {
  134. versionName in Android /:= Some(_)
  135. } ++ Option(flavor.getMinSdkVersion).toList.map {
  136. minSdkVersion in Android /:= _.getApiString
  137. } ++ Option(flavor.getTargetSdkVersion).toList.map {
  138. targetSdkVersion in Android /:= _.getApiString
  139. } ++ Option(flavor.getRenderscriptTargetApi).toList.map {
  140. rsTargetApi in Android /:= _.toString
  141. } ++ Option(flavor.getRenderscriptSupportModeEnabled).toList.map {
  142. rsSupportMode in Android /:= _
  143. } ++ Option(flavor.getMultiDexEnabled).toList.map {
  144. dexMulti in Android /:= _
  145. } ++ Option(flavor.getMultiDexKeepFile).toList.map {
  146. dexMainFileClasses in Android /:= IO.readLines(_, IO.utf8)
  147. } ++ Option(model.getPackagingOptions).toList.map {
  148. p => packagingOptions in Android /:= PackagingOptions(
  149. p.getExcludes.asScala.toList, p.getPickFirsts.asScala.toList, p.getMerges.asScala.toList
  150. )
  151. }
  152. val v = ap.getVariants.asScala.head
  153. val art = v.getMainArtifact
  154. def libraryDependency(m: MavenCoordinates) = {
  155. val module = m.getGroupId % m.getArtifactId % m.getVersion intransitive()
  156. val mID = if (m.getPackaging != "aar") module else Dependencies.aar(module)
  157. val classifier = Option(m.getClassifier)
  158. libraryDependencies /+= classifier.fold(mID)(mID.classifier)
  159. }
  160.  
  161. val androidLibraries = art.getDependencies.getLibraries.asScala.toList
  162. val (aars,projects) = androidLibraries.partition(_.getProject == null)
  163. val (resolved,localAars) = aars.partition(a => Option(a.getResolvedCoordinates.getGroupId).exists(_.nonEmpty))
  164. val localAar = localAars.toList map {
  165. android.Keys.localAars in Android /+= _.getBundle
  166. }
  167. def dependenciesOf[A](l: A, seen: Set[File] = Set.empty)(children: A => List[A])(fileOf: A => File): List[A] = {
  168. val deps = children(l) filterNot(l => seen(fileOf(l)))
  169. deps ++ deps.flatMap(d => dependenciesOf(d, seen ++ deps.map(fileOf))(children)(fileOf))
  170. }
  171. def aarDependencies(l: AndroidLibrary): List[AndroidLibrary] =
  172. dependenciesOf(l)(_.getLibraryDependencies.asScala.toList)(_.getBundle)
  173. def javaDependencies(l: JavaLibrary): List[JavaLibrary] =
  174. dependenciesOf(l)(_.getDependencies.asScala.toList)(_.getJarFile)
  175. val allAar = (resolved ++ resolved.flatMap(aarDependencies)).groupBy(
  176. _.getBundle.getCanonicalFile).map(_._2.head).toList
  177.  
  178. val javalibs = art.getDependencies.getJavaLibraries.asScala.toList
  179. val allJar = (javalibs ++ javalibs.flatMap(javaDependencies)).groupBy(
  180. _.getJarFile.getCanonicalFile).map(_._2.head).toList
  181.  
  182. val libs = allAar ++ allJar filter (j =>
  183. Option(j.getResolvedCoordinates.getGroupId).exists(_.nonEmpty)) map { j =>
  184. libraryDependency(j.getResolvedCoordinates)
  185. }
  186.  
  187. val standard = List(
  188. resolvers /++= repos,
  189. platformTarget in Android /:= ap.getCompileTarget,
  190. name /:= ap.getName,
  191. javacOptions in Compile /++= "-source" :: sourceVersion :: "-target" :: targetVersion :: Nil,
  192. buildConfigOptions in Android /++= flavor.getBuildConfigFields.asScala.toList map { case (key, field) =>
  193. (field.getType, key, field.getValue)
  194. },
  195. resValues in Android /++= flavor.getResValues.asScala.toList map { case (key, field) =>
  196. (field.getType, key, field.getValue)
  197. },
  198. debugIncludesTests in Android /:= false, // default because can't express it easily otherwise
  199. proguardOptions in Android /++= flavor.getProguardFiles.asScala.toList.flatMap(IO.readLines(_, IO.utf8)),
  200. manifestPlaceholders in Android /++= flavor.getManifestPlaceholders.asScala.toMap map { case (k,o) => (k,o.toString) },
  201. projectLayout in Android /:= new ProjectLayout.Wrapped(ProjectLayout(base)) {
  202. override def manifest = sourceProvider.getManifestFile
  203. override def javaSource = sourceProvider.getJavaDirectories.asScala.head
  204. override def resources = sourceProvider.getResourcesDirectories.asScala.head
  205. override def res = sourceProvider.getResDirectories.asScala.head
  206. override def renderscript = sourceProvider.getRenderscriptDirectories.asScala.head
  207. override def aidl = sourceProvider.getAidlDirectories.asScala.head
  208. override def assets = sourceProvider.getAssetsDirectories.asScala.head
  209. override def jniLibs = sourceProvider.getJniLibsDirectories.asScala.head
  210. }
  211. )
  212. val p = Project(base = base, id = ap.getName).settings(
  213. (if (discovery.isApplication) Plugin.androidBuild else Plugin.androidBuildAar): _*).settings(
  214. standard.map(_.setting): _*).settings(optional.map(_.setting): _*).settings(
  215. libs.map(_.setting): _*).settings(localAar.map(_.setting): _*)
  216. val sp = SbtProject(ap.getName, base, discovery.isApplication, projects.map(_.getProject.replace(":","")).toSet, optional ++ libs ++ localAar ++ standard)
  217. (visited, sp :: subprojects)//(p, projects.map(_.getProject.replace(":","")).toSet) :: subprojects)
  218. } else
  219. (visited, subprojects)
  220. } finally {
  221. c.close()
  222. }
  223. }
  224. }
  225.  
  226. object Serializer {
  227. def enc[T : Encoder](t: T): String = implicitly[Encoder[T]].encode(t)
  228.  
  229. trait Encoder[T] {
  230. def encode(t: T): String
  231. }
  232.  
  233. implicit val stringEncoder = new Encoder[String] {
  234. def encode(s: String) = "raw\"\"\"" + s + "\"\"\""
  235. }
  236. implicit val moduleEncoder = new Encoder[ModuleID] {
  237. def encode(m: ModuleID) = {
  238. val base = s""""${m.organization}" % "${m.name}" % "${m.revision}""""
  239. base + m.configurations.fold("")(c => s""" % "$c"""") + m.explicitArtifacts.map(
  240. a =>
  241. if (a.classifier.isDefined) {
  242. s" classifier(${enc(a.classifier.get)})"
  243. } else
  244. s""" artifacts(Artifact(${enc(a.name)}, ${enc(a.`type`)}, ${enc(a.extension)}))"""
  245. ).mkString("")
  246. }
  247. }
  248. implicit def seqEncoder[T : Encoder] = new Encoder[Seq[T]] {
  249. def encode(l: Seq[T]) = if (l.isEmpty) "Nil" else "Seq(" + l.map(i => enc(i)).mkString(",") + ")"
  250. }
  251. implicit val packagingOptionsEncoding = new Encoder[PackagingOptions] {
  252. def encode(p: PackagingOptions) =
  253. s"android.Keys.PackagingOptions(${enc(p.excludes)}, ${enc(p.pickFirsts)}, ${enc(p.merges)})"
  254. }
  255. implicit val fileEncoder = new Encoder[File] {
  256. def encode(f: File) = s"file(${enc(f.getAbsolutePath)})"
  257. }
  258. implicit val antProjectLayoutEncoding = new Encoder[ProjectLayout.Ant] {
  259. def encode(p: ProjectLayout.Ant) =
  260. s"ProjectLayout.Ant(${enc(p.base)})"
  261. }
  262. implicit val gradleProjectLayoutEncoding = new Encoder[ProjectLayout.Gradle] {
  263. def encode(p: ProjectLayout.Gradle) =
  264. s"ProjectLayout.Gradle(${enc(p.base)})"
  265. }
  266. implicit val ProjectLayoutEncoding = new Encoder[ProjectLayout] {
  267. def encode(p: ProjectLayout) = p match {
  268. case x: ProjectLayout.Ant => enc(x)
  269. case x: ProjectLayout.Gradle => enc(x)
  270. case x: ProjectLayout.Wrapped =>
  271. s"""
  272. | new ProjectLayout {
  273. | override def base = ${enc(x.base)}
  274. | override def resources = ${enc(x.resources)}
  275. | override def testSources = ${enc(x.testSources)}
  276. | override def sources = ${enc(x.sources)}
  277. | override def javaSource = ${enc(x.javaSource)}
  278. | override def libs = ${enc(x.libs)}
  279. | override def gen = ${enc(x.gen)}
  280. | override def testRes = ${enc(x.testRes)}
  281. | override def manifest = ${enc(x.manifest)}
  282. | override def scalaSource = ${enc(x.scalaSource)}
  283. | override def aidl = ${enc(x.aidl)}
  284. | override def bin = ${enc(x.bin)}
  285. | override def renderscript = ${enc(x.renderscript)}
  286. | override def testScalaSource = ${enc(x.testScalaSource)}
  287. | override def testAssets = ${enc(x.testAssets)}
  288. | override def jni = ${enc(x.jni)}
  289. | override def assets = ${enc(x.assets)}
  290. | override def testJavaSource = ${enc(x.testJavaSource)}
  291. | override def jniLibs = ${enc(x.jniLibs)}
  292. | override def res = ${enc(x.res)}
  293. | }
  294. |""".stripMargin
  295. }
  296. }
  297. implicit val wrappedProjectLayoutEncoding = new Encoder[ProjectLayout.Wrapped] {
  298. def encode(p: ProjectLayout.Wrapped) =
  299. s"new ProjectLayout.Wrapped(${enc(p.wrapped)})"
  300. }
  301. implicit val boolEncoder = new Encoder[Boolean] {
  302. def encode(b: Boolean) = b.toString
  303. }
  304. implicit val intEncoder = new Encoder[Int] {
  305. def encode(i: Int) = i.toString
  306. }
  307. implicit def listEncoder[T : Encoder] = new Encoder[List[T]] {
  308. def encode(l: List[T]) = if (l.isEmpty) "Nil" else "List(" + l.map(i => enc(i)).mkString(",\n ") + ")"
  309. }
  310. implicit def optionEncoder[T : Encoder] = new Encoder[Option[T]] {
  311. def encode(l: Option[T]) = if (l.isEmpty) "None" else "Some(" + enc(l.get) + ")"
  312. }
  313. implicit def someEncoder[T : Encoder] = new Encoder[Some[T]] {
  314. def encode(l: Some[T]) = "Some(" + enc(l.get) + ")"
  315. }
  316. implicit val resolverEncoder = new Encoder[Resolver] {
  317. def encode(r: Resolver) = r match {
  318. case MavenRepository(n, root) => enc(n) + " at " + enc(root)
  319. case _ => throw new UnsupportedOperationException("Cannot handle: " + r)
  320. }
  321. }
  322. implicit def tuple3Encoder[A : Encoder,B : Encoder,C : Encoder] = new Encoder[(A,B,C)] {
  323. override def encode(t: (A, B, C)) = s"(${enc(t._1)}, ${enc(t._2)}, ${enc(t._3)})"
  324. }
  325. implicit def tuple2Encoder[A : Encoder,B : Encoder] = new Encoder[(A,B)] {
  326. override def encode(t: (A, B)) = s"(${enc(t._1)}, ${enc(t._2)})"
  327. }
  328. implicit def mapEncoder[A : Encoder,B : Encoder] = new Encoder[Map[A,B]] {
  329. override def encode(t: Map[A, B]) = s"Map(${t.toList.map(e => enc(e)).mkString(",\n ")})"
  330. }
  331.  
  332. sealed trait Op {
  333. def serialized: String
  334. }
  335.  
  336. def serialize[T : Manifest, U : Encoder](k: SettingKey[T], op: String, value: U) =
  337. key(k) + " " + op + " " + enc(value)
  338. def serialize[T : Manifest, U : Encoder](k: TaskKey[T], op: String, value: U) =
  339. key(k) + " " + op + " " + enc(value)
  340. def config(s: Scope) =
  341. s.config.toOption.fold("")(c => s""" in config("${c.name}")""")
  342. def typeName[T](implicit manifest: Manifest[T]): String = {
  343. def capitalized(m: Manifest[_]) = {
  344. if (m.runtimeClass.isPrimitive)
  345. m.runtimeClass.getName.capitalize
  346. else m.runtimeClass.getName
  347. }
  348. val typename = capitalized(manifest)
  349. val types = if (manifest.typeArguments.isEmpty) "" else {
  350. s"[${manifest.typeArguments.map(m => typeName(m)).mkString(",")}]"
  351. }
  352. (typename + types).replace("$",".") // replace hack, better solution?
  353. }
  354. def key[T : Manifest](k: SettingKey[T]): String = {
  355. s"""SettingKey[${typeName[T]}]("${k.key.label}")""" + config(k.scope)
  356. }
  357. def key[T : Manifest](k: TaskKey[T]): String = {
  358. s"""TaskKey[${typeName[T]}]("${k.key.label}")""" + config(k.scope)
  359. }
  360. }
  361.  
  362. object GradleBuildSerializer {
  363. import Serializer._
  364. case class SbtProject(id: String, base: File, isApplication: Boolean, dependencies: Set[String], settings: Seq[SbtSetting]) {
  365. def escaped(s: String) = {
  366. val needEscape = s.zipWithIndex exists { case (c, i) =>
  367. (i == 0 && !Character.isJavaIdentifierStart(c)) || (i != 0 && !Character.isJavaIdentifierPart(c))
  368. }
  369. if (needEscape) {
  370. s"`$s`"
  371. } else s
  372. }
  373. lazy val project = Project(base = base, id = id).settings(
  374. (if (isApplication) Plugin.androidBuild else Plugin.androidBuildAar): _*).settings(
  375. settings.map(_.setting):_*)
  376. lazy val dependsOnProjects = {
  377. if (dependencies.nonEmpty) ".dependsOn(" + dependencies.map(escaped).mkString(",") + ")" else ""
  378. }
  379. lazy val dependsOnSettings = {
  380. if (dependencies.nonEmpty) {
  381. val depSettings = dependencies map { d =>
  382. s"""
  383. | collectResources in Android <<=
  384. | collectResources in Android dependsOn (compile in Compile in ${escaped(d)}),
  385. | compile in Compile <<= compile in Compile dependsOn(
  386. | sbt.Keys.`package` in Compile in ${escaped(d)}),
  387. | localProjects in Android += LibraryProject(${escaped(d)}.base)
  388. |""".
  389. stripMargin
  390. } mkString ",\n"
  391. s".settings($depSettings)"
  392. } else ""
  393. }
  394. // TODO if (loaded) to selectively enable/disable .sbt file loading
  395. lazy val serialized =
  396. s"""
  397. |val ${escaped(id)} = Project(id = ${enc(id)}, base = ${enc(base)}).settings(
  398. | ${if (isApplication) "android.Plugin.androidBuild" else "android.Plugin.androidBuildAar"}:_*).settings(
  399. | ${settings.map(_.serialized).mkString(",\n ")}
  400. |)$dependsOnProjects$dependsOnSettings
  401. """.stripMargin
  402. }
  403.  
  404. case class ProjectNode(p: SbtProject, dependencies: Set[String])
  405. def toposort(ps: List[SbtProject]): List[SbtProject] = {
  406. /*
  407. L ← Empty list that will contain the sorted elements
  408. S ← Set of all nodes with no incoming edges
  409. while S is non-empty do
  410. remove a node n from S
  411. add n to tail of L
  412. for each node m with an edge e from n to m do
  413. remove edge e from the graph
  414. if m has no other incoming edges then
  415. insert m into S
  416. if graph has edges then
  417. return error (graph has at least one cycle)
  418. else
  419. return L (a topologically sorted order)
  420. */
  421. @tailrec
  422. def doSort(S: List[ProjectNode], L: List[ProjectNode], ms: List[ProjectNode]): List[ProjectNode] = S match {
  423. case n :: nS =>
  424. val l1 = n :: L
  425. val (s1, ms1) = ms.foldLeft((nS, List.empty[ProjectNode])) { case ((ns2, ms2), m) =>
  426. val m2 = m.copy(dependencies = m.dependencies - n.p.id)
  427. if (m2.dependencies.isEmpty)
  428. (m2 :: ns2, ms2)
  429. else
  430. (ns2, m2 :: ms2)
  431. }
  432.  
  433. doSort(s1, l1, ms1)
  434. case Nil =>
  435. if (ms.nonEmpty)
  436. throw new IllegalStateException("Project graph is cyclical")
  437. L
  438. }
  439. val graph = ps.map(p => ProjectNode(p, p.dependencies))
  440. val (s, ms) = graph.partition(_.dependencies.isEmpty)
  441. doSort(s, Nil, ms).map(_.p).reverse
  442. }
  443.  
  444. import language.existentials
  445. case class SbtSetting(serialized: String, setting: Def.Setting[_])
  446. object Op {
  447. case object := extends Op {
  448. val serialized = ":="
  449. def apply[T : Encoder : Manifest](lhs: SettingKey[T], rhs: T) = SbtSetting(serialize(lhs, serialized, rhs), lhs := rhs)
  450. def apply[T : Encoder : Manifest](lhs: TaskKey[T], rhs: T) = SbtSetting(serialize(lhs, serialized, rhs), lhs := rhs)
  451. }
  452. case object += extends Op {
  453. val serialized = "+="
  454. def apply[T : Manifest,U : Encoder](lhs: SettingKey[T], rhs: U)(implicit m: Append.Value[T,U]) = SbtSetting(serialize(lhs, serialized, rhs), lhs += rhs)
  455. def apply[T : Manifest,U : Encoder](lhs: TaskKey[T], rhs: U)(implicit m: Append.Value[T,U]) = SbtSetting(serialize(lhs, serialized, rhs), lhs += rhs)
  456. }
  457. case object ++= extends Op {
  458. val serialized = "++="
  459. def apply[T : Manifest, U : Encoder](lhs: SettingKey[T], rhs: U)(implicit m: Append.Values[T,U]) = SbtSetting(serialize(lhs, serialized, rhs), lhs ++= rhs)
  460. def apply[T : Manifest, U : Encoder](lhs: TaskKey[T], rhs: U)(implicit m: Append.Values[T,U]) = SbtSetting(serialize(lhs, serialized, rhs), lhs ++= rhs)
  461. }
  462. }
  463.  
  464. implicit class SerializableSettingKey[T : Encoder : Manifest](val k: SettingKey[T]) {
  465. def /:=(t: T) = Op := (k, t)
  466. def /+=[U : Encoder](u: U)(implicit m: Append.Value[T,U]) = Op += (k, u)
  467. def /++=[U : Encoder](u: U)(implicit m: Append.Values[T,U]) = Op ++= (k, u)
  468. }
  469. implicit class SerializableTaskKey[T : Encoder : Manifest](val k: TaskKey[T]) {
  470. def /:=(t: T) = Op := (k, t)
  471. def /+=[U : Encoder](u: U)(implicit m: Append.Value[T,U]) = Op += (k, u)
  472. def /++=[U : Encoder](u: U)(implicit m: Append.Values[T,U]) = Op ++= (k, u)
  473. }
  474. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement