├── project └── build.properties ├── .gitignore ├── .devcontainer └── devcontainer.json ├── README.md ├── src ├── main │ └── scala │ │ ├── Chapter05_Testing.scala │ │ ├── helpers │ │ └── ZIOAppDebug.scala │ │ ├── Chapter08_Shared_State.scala │ │ ├── Chapter03_Superpowers.scala │ │ ├── Chapter06_Failure.scala │ │ ├── Chapter04_Initialization.scala │ │ ├── Chapter07_Composability.scala │ │ └── Chapter09_Resilience.scala └── test │ └── scala │ ├── Chapter09_ResilienceSpec.scala │ └── Chapter05_TestingSpec.scala ├── LICENSE ├── sbt └── sbt.bat /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.10.2 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bsp/ 2 | /.idea/ 3 | /target/ 4 | /project*/ 5 | /.bloop/ 6 | /.metals/ 7 | /.vscode/ 8 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scala-sbt-codespaces-template", 3 | //https://hub.docker.com/r/sbtscala/scala-sbt/tags 4 | "image": "sbtscala/scala-sbt:eclipse-temurin-jammy-21.0.2_13_1.10.2_3.5.0", 5 | "customizations": { 6 | "vscode": { 7 | "extensions": [ 8 | // Scala 9 | "scala-lang.scala", 10 | "scalameta.metals" 11 | ] 12 | } 13 | }, 14 | "remoteUser": "sbtuser" 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Effect Oriented Programming Book Examples 2 | 3 | [Buy the book](https://effectorientedprogramming.com/) 4 | 5 | There are two ways to run these examples: 6 | 7 | ## In the Cloud 8 | 9 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/EffectOrientedProgramming/examples) 10 | *Note: It takes a few minutes for the environment to set itself up.* 11 | 12 | ## On Your Local Machine 13 | 1. [Install Scala & Tools](https://effectorientedprogramming.com/guide/scalatools/) 14 | 1. [Run Examples](https://effectorientedprogramming.com/guide/examples/) 15 | -------------------------------------------------------------------------------- /src/main/scala/Chapter05_Testing.scala: -------------------------------------------------------------------------------- 1 | package Chapter05_Testing 2 | 3 | import zio.* 4 | import zio.direct.* 5 | 6 | val coinToss = 7 | defer: 8 | if Random.nextBoolean.run then 9 | ZIO.debug("Heads").run 10 | ZIO.succeed("Heads").run 11 | else 12 | ZIO.debug("Tails").run 13 | ZIO.fail("Tails").run 14 | 15 | val flipFive = 16 | defer: 17 | ZIO 18 | .collectAllSuccesses: 19 | List.fill(5): 20 | coinToss 21 | .run 22 | .size 23 | 24 | object App0 extends helpers.ZIOAppDebug: 25 | def run = 26 | flipFive 27 | // Heads 28 | // Tails 29 | // Tails 30 | // Heads 31 | // Tails 32 | // Result: 2 33 | 34 | 35 | val nightlyBatch = 36 | ZIO.sleep(24.hours).debug("Parsing CSV") -------------------------------------------------------------------------------- /src/main/scala/helpers/ZIOAppDebug.scala: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import zio.* 4 | 5 | 6 | // Enables us to add a debug to print the output of the program 7 | trait ZIOAppDebug: 8 | self => 9 | 10 | type Environment = Any 11 | 12 | val bootstrap: ZLayer[ZIOAppArgs, Any, Any] = ZLayer.empty 13 | 14 | val environmentTag: EnvironmentTag[Any] = EnvironmentTag[Any] 15 | 16 | def run: ZIO[ZIOAppArgs & Scope, Any, Any] 17 | 18 | def main(args: Array[String]): Unit = 19 | val app = new ZIOAppDefault: 20 | override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] = 21 | self.bootstrap 22 | 23 | override def run: ZIO[ZIOAppArgs & Scope, Any, Any] = 24 | // this tries to create consistency for how we run and print output between the book and the examples 25 | ZIO.scoped: 26 | self.run 27 | .tapSome: 28 | case result if !result.isInstanceOf[Unit] => 29 | Console.printLine: 30 | s"Result: $result" 31 | 32 | app.main(args) 33 | -------------------------------------------------------------------------------- /src/test/scala/Chapter09_ResilienceSpec.scala: -------------------------------------------------------------------------------- 1 | package Chapter09_Resilience 2 | 3 | import zio.* 4 | import zio.direct.* 5 | import zio.test.* 6 | 7 | object Test0 extends ZIOSpecDefault: 8 | def spec = 9 | test("long testZ"): 10 | defer: 11 | ZIO.sleep(1.hour).run 12 | assertCompletes 13 | @@ TestAspect.withLiveClock 14 | @@ TestAspect.timeout(1.second) 15 | // - long testZ 16 | // Timeout of 1 s exceeded. 17 | 18 | 19 | import zio.test.{assertTrue, test} 20 | 21 | val troublesomeTestCase = 22 | test("flaky test!"): 23 | defer: 24 | val wasSuccessful = spottyLogic.run 25 | assertTrue(wasSuccessful) 26 | 27 | object Test2 extends ZIOSpecDefault: 28 | def spec = 29 | troublesomeTestCase 30 | // Failed! 31 | // - flaky test! 32 | // ✗ Result was false 33 | // wasSuccessful 34 | // 35 | 36 | 37 | object Test3 extends ZIOSpecDefault: 38 | def spec = 39 | troublesomeTestCase @@ TestAspect.flaky 40 | // Failed! 41 | // Failed! 42 | // Success! 43 | // + flaky test! 44 | -------------------------------------------------------------------------------- /src/main/scala/Chapter08_Shared_State.scala: -------------------------------------------------------------------------------- 1 | package Chapter08_Shared_State 2 | 3 | import zio.* 4 | import zio.direct.* 5 | 6 | import java.util.concurrent.atomic.AtomicInteger 7 | 8 | def parallel( 9 | num: Int 10 | )(effect: ZIO[Any, Nothing, Unit]) = 11 | ZIO 12 | .foreachPar(Range(0, num)): 13 | _ => effect 14 | .unit 15 | 16 | class AttemptCounter extends AtomicInteger: 17 | def increment = 18 | incrementAndGet() 19 | 20 | object App0 extends helpers.ZIOAppDebug: 21 | def run = 22 | val num = 30_000 23 | var counter = 0 24 | val increment = 25 | ZIO.succeed: 26 | counter = counter + 1 27 | 28 | parallel(num): 29 | increment 30 | .as: 31 | s"Lost updates: ${num - counter}" 32 | // Result: Lost updates: 11 33 | 34 | 35 | object App1 extends helpers.ZIOAppDebug: 36 | def increment(count: Ref[Int]) = 37 | count.update: 38 | value => value + 1 39 | 40 | def run = 41 | defer: 42 | val counter = Ref.make(0).run 43 | parallel(30_000): 44 | increment(counter) 45 | .run 46 | s"counter: ${counter.get.run}" 47 | // Result: counter: 30000 48 | 49 | 50 | object App2 extends helpers.ZIOAppDebug: 51 | val attempts = AttemptCounter() 52 | 53 | def increment(count: Ref[Int]) = 54 | count.update: 55 | value => 56 | attempts.increment 57 | value + 1 58 | 59 | def run = 60 | defer: 61 | val counter = Ref.make(0).run 62 | parallel(30_000): 63 | increment(counter) 64 | .run 65 | 66 | s""" 67 | counter: ${counter.get.run} 68 | attempts: ${attempts.get} 69 | """.stripMargin 70 | // Result: 71 | // counter: 30000 72 | // attempts: 30377 73 | 74 | 75 | object App3 extends helpers.ZIOAppDebug: 76 | val attempts = AttemptCounter() 77 | 78 | def increment(count: Ref.Synchronized[Int]) = 79 | count.updateZIO: 80 | value => 81 | ZIO.succeed: 82 | attempts.increment 83 | value + 1 84 | 85 | def run = 86 | defer: 87 | val counter = 88 | Ref.Synchronized.make(0).run 89 | parallel(30_000): 90 | increment(counter) 91 | .run 92 | 93 | s""" 94 | counter: ${counter.get.run} 95 | attempts: ${attempts.get} 96 | """.stripMargin 97 | // Result: 98 | // counter: 30000 99 | // attempts: 30000 100 | -------------------------------------------------------------------------------- /src/test/scala/Chapter05_TestingSpec.scala: -------------------------------------------------------------------------------- 1 | package Chapter05_Testing 2 | 3 | import zio.* 4 | import zio.direct.* 5 | import zio.test.* 6 | 7 | object Test0 extends ZIOSpecDefault: 8 | def spec = 9 | test("Basic"): 10 | assertTrue(1 == 1) 11 | // + Basic 12 | 13 | 14 | object Test1 extends ZIOSpecDefault: 15 | def spec = 16 | test("Only the last assertTrue matters"): 17 | assertTrue(1 != 1) // Ignored 18 | assertTrue(1 == 1) 19 | // + Only the last assertTrue matters 20 | 21 | 22 | object Test2 extends ZIOSpecDefault: 23 | def spec = 24 | test("Multiple Boolean expressions"): 25 | assertTrue(1 == 1, 2 == 2, 3 == 3) 26 | // + Multiple Boolean expressions 27 | 28 | 29 | object Test3 extends ZIOSpecDefault: 30 | def spec = 31 | test("Combine using operators"): 32 | assertTrue(1 == 0) || 33 | assertTrue(2 == 2) && 34 | assertTrue(3 == 3) 35 | // + Combine using operators 36 | 37 | 38 | object Test4 extends ZIOSpecDefault: 39 | def spec = 40 | test("negation"): 41 | !assertTrue(42 == 47) 42 | // + negation 43 | 44 | 45 | object Test5 extends ZIOSpecDefault: 46 | def spec = 47 | test("Effect as test"): 48 | defer: 49 | ZIO.debug("Executing logic").run 50 | assertCompletes 51 | // Executing logic 52 | // + Effect as test 53 | 54 | 55 | def showLabel(label: String) = 56 | defer: 57 | ZIO.debug(s"Running $label").run 58 | assertCompletes 59 | 60 | val effectA = showLabel("A") 61 | 62 | object Test8 extends ZIOSpecDefault: 63 | def spec = 64 | test("Case A"): 65 | effectA 66 | // Running A 67 | // + Case A 68 | 69 | 70 | object Test9 extends ZIOSpecDefault: 71 | val effectB = showLabel("B") 72 | 73 | def spec = 74 | suite("Suite of Tests")( 75 | test("Case A in suite")(effectA), 76 | test("Case B in suite")(effectB), 77 | ) 78 | // Running A 79 | // Running B 80 | // + Suite of Tests 81 | // + Case A in suite 82 | // + Case B in suite 83 | 84 | 85 | import zio.test.test 86 | 87 | def testCase(label: String) = 88 | test(s"Case $label in a value"): 89 | showLabel(label) 90 | 91 | val testA = testCase("A") 92 | val testB = testCase("B") 93 | 94 | object Test12 extends ZIOSpecDefault: 95 | def spec = 96 | suite("A Suite of Tests")(testA, testB) 97 | // Running A 98 | // Running B 99 | // + A Suite of Tests 100 | // + Case A in a value 101 | // + Case B in a value 102 | 103 | 104 | case class Material(brittleness: Int) 105 | 106 | object Material: 107 | val wood = 108 | ZLayer.succeed: 109 | Material(brittleness = 5) 110 | val plastic = 111 | ZLayer.succeed: 112 | Material(brittleness = 10) 113 | 114 | case class Nailer(force: Int) 115 | 116 | object Nailer: 117 | val hand = 118 | ZLayer.succeed: 119 | Nailer(force = 4) 120 | 121 | val robotic = 122 | ZLayer.succeed: 123 | Nailer(force = 11) 124 | 125 | val testNailerWithMaterial = 126 | defer: 127 | val material = ZIO.service[Material].run 128 | val nailer = ZIO.service[Nailer].run 129 | assertTrue( 130 | nailer.force < material.brittleness 131 | ) 132 | 133 | object Test16 extends ZIOSpecDefault: 134 | def spec = 135 | suite("Construction Combinations")( 136 | test("Wood with hand nailer"): 137 | testNailerWithMaterial 138 | .provide(Material.wood, Nailer.hand) 139 | , 140 | test("Plastic with hand nailer"): 141 | testNailerWithMaterial.provide( 142 | Material.plastic, 143 | Nailer.hand, 144 | ) 145 | , 146 | test("Plastic with robo nailer"): 147 | testNailerWithMaterial.provide( 148 | Material.plastic, 149 | Nailer.robotic, 150 | ), 151 | ) 152 | // + Construction Combinations 153 | // + Wood with hand nailer 154 | // + Plastic with hand nailer 155 | // - Plastic with robo nailer 156 | // ✗ 11 was not less than 10 157 | // nailer.force < material.brittleness 158 | // .force = 11 159 | // nailer = Nailer(force = 11) 160 | // 161 | 162 | 163 | object Test17 extends ZIOSpecDefault: 164 | def spec = 165 | test("Flip 5 times"): 166 | defer: 167 | TestRandom 168 | .feedBooleans(true, true, true) 169 | .run 170 | TestRandom 171 | .feedBooleans(false, false) 172 | .run 173 | val heads = 174 | flipFive.debug("Number of Heads").run 175 | assertTrue(heads == 3) 176 | // Tails 177 | // Tails 178 | // Heads 179 | // Heads 180 | // Heads 181 | // Number of Heads: 3 182 | // + Flip 5 times 183 | 184 | 185 | val timeTravel = 186 | TestClock.adjust: 187 | 24.hours 188 | 189 | object Test19 extends ZIOSpecDefault: 190 | def spec = 191 | test("Batch runs after 24 hours"): 192 | defer: 193 | nightlyBatch.zipPar(timeTravel).run 194 | assertCompletes 195 | // Parsing CSV: () 196 | // + Batch runs after 24 hours 197 | -------------------------------------------------------------------------------- /src/main/scala/Chapter03_Superpowers.scala: -------------------------------------------------------------------------------- 1 | package Chapter03_Superpowers 2 | 3 | import zio.* 4 | import zio.direct.* 5 | 6 | import Scenario.* 7 | 8 | var currentScenario: Scenario = NeverWorks 9 | 10 | enum Scenario: 11 | case Successful 12 | case NeverWorks 13 | case Slow 14 | case WorksOnTryInner(ref: Ref[Int]) 15 | 16 | def simulate[E, A]( 17 | effect: ZIO[Scenario & Scope, E, A] 18 | ) = 19 | defer: 20 | ZIO 21 | .succeed: 22 | currentScenario = this 23 | .run 24 | ZIO 25 | .scoped(effect) 26 | .provide(ZLayer.succeed(this)) 27 | .run 28 | 29 | object Scenario: 30 | // A bit of trickery here, so that the 31 | // reader thinks they're seeing 32 | // just-another-enum case, even though it's 33 | // calling some unsafe stuff behind the 34 | // scenes to create the *real* enum case 35 | 36 | def WorksOnThirdTry: WorksOnTryInner = 37 | Unsafe.unsafe: 38 | implicit unsafe => 39 | WorksOnTryInner( 40 | Runtime 41 | .default 42 | .unsafe 43 | .run(Ref.make(0)) 44 | .getOrThrow() 45 | ) 46 | 47 | def saveUser(username: String) = 48 | val succeed = 49 | ZIO.succeed: 50 | "User saved" 51 | 52 | val fail = 53 | ZIO 54 | .fail: 55 | "**Database crashed!!**" 56 | .tapError: 57 | error => 58 | ZIO.debug: 59 | "Log: " + error 60 | 61 | val logic = 62 | defer: 63 | val scenario = 64 | ZIO.service[Scenario].run 65 | ZIO 66 | .debug("Attempting to save user") 67 | .run 68 | scenario match 69 | case Scenario.NeverWorks => 70 | fail.run 71 | 72 | case Scenario.Slow => 73 | ZIO.sleep(1.minute).run 74 | succeed.run 75 | 76 | case Scenario.WorksOnTryInner(ref) => 77 | val numCalls = 78 | ref.getAndUpdate(_ + 1).run 79 | if numCalls == 2 then 80 | succeed.run 81 | else 82 | fail.run 83 | 84 | case Scenario.Successful => 85 | succeed.run 86 | logic 87 | end saveUser 88 | 89 | def sendToManualQueue(username: String) = 90 | ZIO.attempt: 91 | s"Sent $username to manual queue" 92 | 93 | def logUserSignup(username: String) = 94 | ZIO.debug: 95 | s"Log: Signup initiated for $userName" 96 | 97 | val userName = "Morty" 98 | 99 | val effect0 = 100 | saveUser: 101 | userName 102 | 103 | import zio.* 104 | 105 | object MyApp extends ZIOAppDefault: 106 | def run = 107 | Successful.simulate: 108 | effect0 109 | 110 | object App0 extends helpers.ZIOAppDebug: 111 | def run = 112 | Successful.simulate: 113 | effect0 114 | // Attempting to save user 115 | // Result: User saved 116 | 117 | 118 | object App1 extends helpers.ZIOAppDebug: 119 | def run = 120 | WorksOnThirdTry.simulate: 121 | effect0 122 | // Attempting to save user 123 | // Log: **Database crashed!!** 124 | // Error: **Database crashed!!** 125 | 126 | 127 | val effect1 = effect0.retryN(2) 128 | 129 | object App2 extends helpers.ZIOAppDebug: 130 | def run = 131 | WorksOnThirdTry.simulate: 132 | effect1 133 | // Attempting to save user 134 | // Log: **Database crashed!!** 135 | // Attempting to save user 136 | // Log: **Database crashed!!** 137 | // Attempting to save user 138 | // Result: User saved 139 | 140 | 141 | object App3 extends helpers.ZIOAppDebug: 142 | def run = 143 | NeverWorks.simulate: 144 | effect1 145 | // Attempting to save user 146 | // Log: **Database crashed!!** 147 | // Attempting to save user 148 | // Log: **Database crashed!!** 149 | // Attempting to save user 150 | // Log: **Database crashed!!** 151 | // Error: **Database crashed!!** 152 | 153 | 154 | val effect2 = 155 | effect1.orElseFail: 156 | "FAILURE: User not saved" 157 | 158 | object App4 extends helpers.ZIOAppDebug: 159 | def run = 160 | NeverWorks.simulate: 161 | effect2 162 | // Attempting to save user 163 | // Log: **Database crashed!!** 164 | // Attempting to save user 165 | // Log: **Database crashed!!** 166 | // Attempting to save user 167 | // Log: **Database crashed!!** 168 | // Error: FAILURE: User not saved 169 | 170 | 171 | val effect3 = 172 | effect2 173 | .timeoutFail("** Save timed out **"): 174 | 5.seconds 175 | 176 | object App5 extends helpers.ZIOAppDebug: 177 | def run = 178 | Slow.simulate: 179 | effect3 180 | // Attempting to save user 181 | // Error: ** Save timed out ** 182 | 183 | 184 | val effect4 = 185 | effect3.orElse: 186 | sendToManualQueue: 187 | userName 188 | 189 | object App6 extends helpers.ZIOAppDebug: 190 | def run = 191 | NeverWorks.simulate: 192 | effect4 193 | // Attempting to save user 194 | // Log: **Database crashed!!** 195 | // Attempting to save user 196 | // Log: **Database crashed!!** 197 | // Attempting to save user 198 | // Log: **Database crashed!!** 199 | // Result: Sent Morty to manual queue 200 | 201 | 202 | val effect5 = 203 | effect4.withFinalizer: 204 | username => logUserSignup(username) 205 | 206 | object App7 extends helpers.ZIOAppDebug: 207 | def run = 208 | Successful.simulate: 209 | effect5 210 | // Attempting to save user 211 | // Log: Signup initiated for Morty 212 | // Result: User saved 213 | 214 | 215 | val effect6 = effect5.timed 216 | 217 | object App8 extends helpers.ZIOAppDebug: 218 | def run = 219 | Successful.simulate: 220 | effect6 221 | // Attempting to save user 222 | // Log: Signup initiated for Morty 223 | // Result: (PT0.002206111S,User saved) 224 | 225 | 226 | val effect7 = 227 | effect6.when(userName != "Morty") 228 | 229 | object App9 extends helpers.ZIOAppDebug: 230 | def run = 231 | Successful.simulate: 232 | effect7 233 | // Result: None 234 | 235 | 236 | object App10 extends helpers.ZIOAppDebug: 237 | def run = 238 | Successful.simulate: 239 | ZIO.debug("Before save") 240 | effect1 241 | // Attempting to save user 242 | // Result: User saved 243 | 244 | 245 | val effect8 = 246 | defer: 247 | ZIO.debug("Before save").run 248 | effect1.run 249 | 250 | object App11 extends helpers.ZIOAppDebug: 251 | def run = 252 | Successful.simulate: 253 | effect8 254 | // Before save 255 | // Attempting to save user 256 | // Result: User saved 257 | 258 | 259 | object App12 extends helpers.ZIOAppDebug: 260 | def run = 261 | Successful.simulate: 262 | defer: 263 | ZIO.debug("**Before**").run 264 | effect8.debug.repeatN(1).run 265 | ZIO.debug("**After**").run 266 | // **Before** 267 | // Before save 268 | // Attempting to save user 269 | // User saved 270 | // Before save 271 | // Attempting to save user 272 | // User saved 273 | // **After** 274 | -------------------------------------------------------------------------------- /src/main/scala/Chapter06_Failure.scala: -------------------------------------------------------------------------------- 1 | package Chapter06_Failure 2 | 3 | import zio.* 4 | import zio.direct.* 5 | 6 | def divide(a: Int, b: Int): Int = 7 | a / b 8 | 9 | case object FailObject 10 | 11 | class FailException extends Exception: 12 | override def toString: String = 13 | "FailException" 14 | 15 | def failureTypes(n: Int) = 16 | n match 17 | case 0 => 18 | ZIO.fail("String fail") 19 | case 1 => 20 | ZIO.fail(FailObject) 21 | case _ => 22 | ZIO.fail(FailException()) 23 | 24 | object App0 extends helpers.ZIOAppDebug: 25 | def run = 26 | defer: 27 | ZIO.debug("Begin failing").run 28 | failureTypes(0).flip.debug.run 29 | failureTypes(1).flip.debug.run 30 | failureTypes(2).flip.debug.run 31 | ZIO.debug("Done failing").run 32 | // Begin failing 33 | // String fail 34 | // FailObject 35 | // FailException 36 | // Done failing 37 | 38 | 39 | def limitFail(n: Int, limit: Int) = 40 | defer: 41 | ZIO.debug(s"Executing step $n").run 42 | if n < limit then 43 | ZIO.succeed(s"Completed step $n").run 44 | else 45 | ZIO.fail(s"Failed at step $n").run 46 | 47 | def shortCircuit(limit: Int) = 48 | defer: 49 | limitFail(0, limit).run 50 | limitFail(1, limit).run 51 | limitFail(2, limit).run 52 | 53 | object App1 extends helpers.ZIOAppDebug: 54 | def run = 55 | shortCircuit(0).flip 56 | // Executing step 0 57 | // Result: Failed at step 0 58 | 59 | 60 | object App2 extends helpers.ZIOAppDebug: 61 | def run = 62 | shortCircuit(1).flip 63 | // Executing step 0 64 | // Executing step 1 65 | // Result: Failed at step 1 66 | 67 | 68 | object App3 extends helpers.ZIOAppDebug: 69 | def run = 70 | shortCircuit(2).flip 71 | // Executing step 0 72 | // Executing step 1 73 | // Executing step 2 74 | // Result: Failed at step 2 75 | 76 | 77 | object App4 extends helpers.ZIOAppDebug: 78 | def run = 79 | shortCircuit(3) 80 | // Executing step 0 81 | // Executing step 1 82 | // Executing step 2 83 | // Result: Completed step 2 84 | 85 | 86 | import Scenario.* 87 | 88 | var currentScenario: Scenario = GPSFailure 89 | 90 | enum Scenario: 91 | case Successful, 92 | TooCold, 93 | NetworkFailure, 94 | GPSFailure 95 | 96 | def simulate[E, A]( 97 | effect: ZIO[Scenario & Scope, E, A] 98 | ) = 99 | defer: 100 | ZIO 101 | .succeed: 102 | currentScenario = this 103 | .run 104 | ZIO 105 | .scoped(effect) 106 | .provide(ZLayer.succeed(this)) 107 | .run 108 | 109 | class GpsException 110 | extends Exception("GPS Failure") 111 | class NetworkException 112 | extends Exception("Network Failure") 113 | 114 | case class Temperature(degrees: Int) 115 | 116 | val getTemperature: ZIO[ 117 | Scenario, 118 | GpsException | NetworkException, 119 | Temperature, 120 | ] = 121 | defer: 122 | val scenario = ZIO.service[Scenario].run 123 | ZIO.debug("Getting Temperature").run 124 | 125 | scenario match 126 | case Scenario.GPSFailure => 127 | ZIO 128 | .fail: 129 | GpsException() 130 | .run 131 | case Scenario.NetworkFailure => 132 | ZIO 133 | .fail: 134 | NetworkException() 135 | .run 136 | case Scenario.TooCold => 137 | ZIO 138 | .succeed: 139 | Temperature(-20) 140 | .run 141 | case Scenario.Successful => 142 | ZIO 143 | .succeed: 144 | Temperature(35) 145 | .run 146 | end match 147 | 148 | object App5 extends helpers.ZIOAppDebug: 149 | def run = 150 | Successful.simulate: 151 | getTemperature 152 | // Getting Temperature 153 | // Result: Temperature(35) 154 | 155 | 156 | object App6 extends helpers.ZIOAppDebug: 157 | def run = 158 | NetworkFailure.simulate: 159 | getTemperature 160 | // Getting Temperature 161 | // Defect: NetworkException: Network Failure 162 | 163 | 164 | object App7 extends helpers.ZIOAppDebug: 165 | def run = 166 | NetworkFailure.simulate: 167 | defer: 168 | getTemperature.run 169 | ZIO.debug("succeeded").run 170 | // Getting Temperature 171 | // Defect: NetworkException: Network Failure 172 | 173 | 174 | object App8 extends helpers.ZIOAppDebug: 175 | val displayTemperature = 176 | getTemperature.catchAll: 177 | case e: Exception => 178 | ZIO.succeed("getTemperature failed") 179 | 180 | def run = 181 | NetworkFailure.simulate: 182 | defer: 183 | val result = displayTemperature.run 184 | ZIO.debug(result).run 185 | // Getting Temperature 186 | // getTemperature failed 187 | 188 | 189 | val notExhaustive = 190 | getTemperature.catchAll: 191 | case ex: NetworkException => 192 | ZIO.succeed: 193 | "Network Unavailable" 194 | 195 | val temperatureAppComplete = 196 | getTemperature.catchAll: 197 | case ex: NetworkException => 198 | ZIO.succeed: 199 | "Network Unavailable" 200 | case ex: GpsException => 201 | ZIO.succeed: 202 | "GPS Hardware Failure" 203 | 204 | object App9 extends helpers.ZIOAppDebug: 205 | def run = 206 | GPSFailure.simulate: 207 | defer: 208 | val result = temperatureAppComplete.run 209 | ZIO.debug(result).run 210 | // Getting Temperature 211 | // GPS Hardware Failure 212 | 213 | 214 | // Note - the error below does not get properly replaced when building on windows 215 | val x = 0 216 | 217 | def check(t: Temperature) = 218 | defer: 219 | ZIO.debug("Checking Temperature").run 220 | if t.degrees > 0 then 221 | ZIO 222 | .succeed: 223 | "Comfortable Temperature" 224 | .run 225 | else 226 | ZIO 227 | .fail: 228 | ClimateFailure("**Too Cold**") 229 | .run 230 | 231 | case class ClimateFailure(message: String) 232 | 233 | object App10 extends helpers.ZIOAppDebug: 234 | def run = 235 | check(Temperature(-20)) 236 | // Checking Temperature 237 | // Error: ClimateFailure(**Too Cold**) 238 | 239 | 240 | val weatherReportFaulty = 241 | defer: 242 | val result = getTemperature.run 243 | check(result).run 244 | 245 | val weatherReport = 246 | weatherReportFaulty.catchAll: 247 | case exception: Exception => 248 | ZIO.debug(exception.getMessage) 249 | case failure: ClimateFailure => 250 | ZIO.debug(failure.message) 251 | 252 | object App11 extends helpers.ZIOAppDebug: 253 | def run = 254 | TooCold.simulate: 255 | weatherReport 256 | // Getting Temperature 257 | // Checking Temperature 258 | // **Too Cold** 259 | 260 | 261 | def getTemperatureOrThrow(): String = 262 | currentScenario match 263 | case Scenario.GPSFailure => 264 | throw GpsException() 265 | case Scenario.NetworkFailure => 266 | throw NetworkException() 267 | case _ => 268 | "35 degrees" 269 | 270 | object App12 extends helpers.ZIOAppDebug: 271 | def run = 272 | NetworkFailure.simulate: 273 | ZIO.succeed: 274 | getTemperatureOrThrow() 275 | // Defect: NetworkException: Network Failure 276 | 277 | 278 | def safeTemperatureApp = 279 | ZIO.attempt: 280 | getTemperatureOrThrow() 281 | 282 | object App13 extends helpers.ZIOAppDebug: 283 | def run = 284 | NetworkFailure.simulate: 285 | safeTemperatureApp.orElse: 286 | ZIO.succeed: 287 | "Could not get temperature" 288 | // Result: Could not get temperature 289 | -------------------------------------------------------------------------------- /src/main/scala/Chapter04_Initialization.scala: -------------------------------------------------------------------------------- 1 | package Chapter04_Initialization 2 | 3 | import zio.* 4 | import zio.direct.* 5 | 6 | trait Bread: 7 | val eat = ZIO.debug("Bread: Eating") 8 | 9 | class BreadStoreBought extends Bread 10 | 11 | val purchaseBread = 12 | defer: 13 | ZIO.debug("Buying bread").run 14 | BreadStoreBought() 15 | 16 | val storeBoughtBread = 17 | ZLayer.fromZIO: 18 | purchaseBread 19 | 20 | val eatBread = 21 | ZIO.serviceWithZIO[Bread]: 22 | bread => bread.eat 23 | 24 | object App0 extends helpers.ZIOAppDebug: 25 | def run = 26 | eatBread.provide: 27 | storeBoughtBread 28 | // Buying bread 29 | // Bread: Eating 30 | 31 | 32 | class Dough: 33 | val letRise = ZIO.debug("Dough: rising") 34 | 35 | object Dough: 36 | val fresh = 37 | ZLayer.fromZIO: 38 | defer: 39 | ZIO.debug("Dough: Mixed").run 40 | Dough() 41 | 42 | trait HeatSource 43 | 44 | class Oven extends HeatSource 45 | 46 | object Oven: 47 | val heated = 48 | ZLayer.fromZIO: 49 | defer: 50 | ZIO.debug("Oven: Heated").run 51 | Oven() 52 | 53 | class BreadHomeMade( 54 | heat: HeatSource, 55 | dough: Dough, 56 | ) extends Bread 57 | 58 | val homeMadeBread = 59 | ZLayer.fromZIO: 60 | defer: 61 | ZIO.debug("BreadHomeMade: Baked").run 62 | BreadHomeMade( 63 | ZIO.service[Oven].run, 64 | ZIO.service[Dough].run, 65 | ) 66 | 67 | object App1 extends helpers.ZIOAppDebug: 68 | def run = 69 | eatBread.provide( 70 | homeMadeBread, 71 | Dough.fresh, 72 | Oven.heated, 73 | ) 74 | // Oven: Heated 75 | // Dough: Mixed 76 | // BreadHomeMade: Baked 77 | // Bread: Eating 78 | 79 | 80 | object Bread: 81 | val storeBought = storeBoughtBread 82 | val homeMade = homeMadeBread 83 | 84 | trait Toast: 85 | val bread: Bread 86 | val heat: HeatSource 87 | val eat = ZIO.debug("Toast: Eating") 88 | 89 | case class ToastFromHeatSource( 90 | bread: Bread, 91 | heat: HeatSource, 92 | ) extends Toast 93 | 94 | object ToastFromHeatSource: 95 | val toasted = 96 | ZLayer.fromZIO: 97 | defer: 98 | ZIO.debug("Toast: Made").run 99 | ToastFromHeatSource( 100 | ZIO.service[Bread].run, 101 | ZIO.service[HeatSource].run, 102 | ) 103 | 104 | class Toaster extends HeatSource 105 | 106 | object Toaster: 107 | val ready = 108 | ZLayer.fromZIO: 109 | defer: 110 | ZIO.debug("Toaster: Ready").run 111 | Toaster() 112 | 113 | case class ToastFromToaster( 114 | bread: Bread, 115 | heat: Toaster, 116 | ) extends Toast 117 | 118 | object ToastFromToaster: 119 | val toasted = 120 | ZLayer.fromZIO: 121 | defer: 122 | ZIO.debug("Toast: Made").run 123 | ToastFromToaster( 124 | ZIO.service[Bread].run, 125 | ZIO.service[Toaster].run, 126 | ) 127 | 128 | object App2 extends helpers.ZIOAppDebug: 129 | def run = 130 | ZIO 131 | .serviceWithZIO[Toast]: 132 | toast => toast.eat 133 | .provide( 134 | ToastFromToaster.toasted, 135 | Dough.fresh, 136 | Bread.homeMade, 137 | // The two HeatSources don't clash: 138 | Oven.heated, 139 | Toaster.ready, 140 | ) 141 | // Toaster: Ready 142 | // Oven: Heated 143 | // Dough: Mixed 144 | // BreadHomeMade: Baked 145 | // Toast: Made 146 | // Toast: Eating 147 | 148 | 149 | object App3 extends helpers.ZIOAppDebug: 150 | def run = 151 | ZIO 152 | .serviceWithZIO[Toast]: 153 | toast => toast.eat 154 | .provide( 155 | ZLayer.Debug.tree, 156 | ToastFromToaster.toasted, 157 | Dough.fresh, 158 | Bread.homeMade, 159 | Oven.heated, 160 | Toaster.ready, 161 | ) 162 | // Toaster: Ready 163 | // Oven: Heated 164 | // Dough: Mixed 165 | // BreadHomeMade: Baked 166 | // Toast: Made 167 | // Toast: Eating 168 | 169 | 170 | object OvenSafe: 171 | val heated = 172 | ZLayer.scoped: 173 | defer: 174 | ZIO.debug("Oven: Heated").run 175 | Oven() 176 | .withFinalizer: 177 | _ => ZIO.debug("Oven: Turning off") 178 | 179 | object App4 extends helpers.ZIOAppDebug: 180 | def run = 181 | eatBread.provide( 182 | Bread.homeMade, 183 | Dough.fresh, 184 | OvenSafe.heated, 185 | ) 186 | // Oven: Heated 187 | // Dough: Mixed 188 | // BreadHomeMade: Baked 189 | // Bread: Eating 190 | // Oven: Turning off 191 | 192 | 193 | class BreadFromFriend extends Bread() 194 | 195 | object Friend: 196 | def forcedFailure(invocations: Int) = 197 | defer: 198 | ZIO 199 | .debug( 200 | s"Attempt $invocations: Failure(Friend Unreachable)" 201 | ) 202 | .run 203 | ZIO 204 | .when(true)( 205 | ZIO.fail( 206 | "Failure(Friend Unreachable)" 207 | ) 208 | ) 209 | .as(???) 210 | .run 211 | ZIO.succeed(BreadFromFriend()).run 212 | 213 | def requestBread = 214 | val worksOnAttempt = 4 215 | var invocations: Ref[Int] = 216 | Unsafe.unsafe: 217 | implicit unsafe => 218 | Runtime 219 | .default 220 | .unsafe 221 | .run(Ref.make(0)) 222 | .getOrThrow() 223 | defer: 224 | val curInvocations = 225 | invocations.updateAndGet(_ + 1).run 226 | if curInvocations < worksOnAttempt then 227 | forcedFailure(curInvocations).run 228 | else 229 | ZIO 230 | .debug( 231 | s"Attempt $curInvocations: Succeeded" 232 | ) 233 | .as: 234 | BreadFromFriend() 235 | .run 236 | end requestBread 237 | end Friend 238 | 239 | object App5 extends helpers.ZIOAppDebug: 240 | def run = 241 | eatBread.provide: 242 | ZLayer.fromZIO: 243 | Friend.requestBread 244 | // Attempt 1: Failure(Friend Unreachable) 245 | // Error: Failure(Friend Unreachable) 246 | 247 | 248 | object App6 extends helpers.ZIOAppDebug: 249 | def run = 250 | eatBread.provide: 251 | ZLayer 252 | .fromZIO: 253 | Friend.requestBread 254 | .orElse: 255 | Bread.storeBought 256 | // Attempt 1: Failure(Friend Unreachable) 257 | // Buying bread 258 | // Bread: Eating 259 | 260 | 261 | object App7 extends helpers.ZIOAppDebug: 262 | def run = 263 | eatBread.provide: 264 | ZLayer.fromZIO: 265 | Friend.requestBread.retryN(1) 266 | // Attempt 1: Failure(Friend Unreachable) 267 | // Attempt 2: Failure(Friend Unreachable) 268 | // Error: Failure(Friend Unreachable) 269 | 270 | 271 | case class RetryConfig(times: Int) 272 | 273 | val configurableBread = 274 | ZLayer.fromZIO: 275 | defer: 276 | val config = 277 | ZIO.service[RetryConfig].run 278 | Friend 279 | .requestBread 280 | .retryN: 281 | config.times 282 | .run 283 | 284 | object App8 extends helpers.ZIOAppDebug: 285 | val retryTwice = 286 | ZLayer.succeed: 287 | RetryConfig(2) 288 | 289 | def run = 290 | eatBread 291 | .provide(configurableBread, retryTwice) 292 | // Attempt 1: Failure(Friend Unreachable) 293 | // Attempt 2: Failure(Friend Unreachable) 294 | // Attempt 3: Failure(Friend Unreachable) 295 | // Error: Failure(Friend Unreachable) 296 | 297 | 298 | import zio.config.magnolia.deriveConfig 299 | 300 | val configDescriptor = 301 | deriveConfig[RetryConfig] 302 | 303 | import zio.config.* 304 | import zio.config.typesafe.* 305 | 306 | val configProvider = 307 | ConfigProvider.fromHoconString: 308 | "{ times: 3 }" 309 | 310 | val configuration = 311 | ZLayer.fromZIO: 312 | read: 313 | configDescriptor.from: 314 | configProvider 315 | 316 | object App9 extends helpers.ZIOAppDebug: 317 | def run = 318 | eatBread.provide( 319 | configurableBread, 320 | configuration, 321 | ) 322 | // Attempt 1: Failure(Friend Unreachable) 323 | // Attempt 2: Failure(Friend Unreachable) 324 | // Attempt 3: Failure(Friend Unreachable) 325 | // Attempt 4: Succeeded 326 | // Bread: Eating 327 | -------------------------------------------------------------------------------- /src/main/scala/Chapter07_Composability.scala: -------------------------------------------------------------------------------- 1 | package Chapter07_Composability 2 | 3 | import zio.* 4 | import zio.direct.* 5 | 6 | import Scenario.* 7 | 8 | var currentScenario: Scenario = 9 | DiskFull // D: D: 10 | 11 | enum Scenario: 12 | case Successful 13 | case HeadlineError 14 | case BoringTopic 15 | case FileSystemError 16 | case WikiSystemError 17 | case AISlow 18 | case DiskFull 19 | 20 | def simulate[E, A]( 21 | effect: ZIO[Scenario & Scope, E, A] 22 | ) = 23 | defer: 24 | ZIO 25 | .succeed: 26 | currentScenario = this 27 | .run 28 | ZIO 29 | .scoped(effect) 30 | .provide(ZLayer.succeed(this)) 31 | .run 32 | end Scenario 33 | 34 | import scala.concurrent.Future 35 | import Scenario.* 36 | 37 | def getHeadline(): Future[String] = 38 | println("Network - Getting headline") 39 | currentScenario match 40 | case Scenario.HeadlineError => 41 | Future.failed: 42 | new Exception( 43 | "Headline not available" 44 | ) 45 | case Scenario.Successful => 46 | Future 47 | .successful("stock market rising!") 48 | case Scenario.WikiSystemError => 49 | Future.successful("Fred built a barn.") 50 | case Scenario.AISlow => 51 | Future.successful("space is big!") 52 | case Scenario.FileSystemError => 53 | Future 54 | .successful("new unicode released!") 55 | case Scenario.BoringTopic => 56 | Future.successful("boring content") 57 | case Scenario.DiskFull => 58 | Future 59 | .successful("human genome sequenced") 60 | end match 61 | end getHeadline 62 | 63 | def findTopicOfInterest( 64 | content: String 65 | ): Option[String] = 66 | println("Analytics - Scanning for topic") 67 | val topics = 68 | List( 69 | "stock market", 70 | "space", 71 | "barn", 72 | "unicode", 73 | "genome", 74 | ) 75 | val res = topics.find(content.contains) 76 | println(s"Analytics - topic: $res") 77 | res 78 | 79 | import scala.util.Either 80 | 81 | case class NoWikiArticle() 82 | 83 | def wikiArticle( 84 | topic: String 85 | ): Either[NoWikiArticle, String] = 86 | println(s"Wiki - articleFor($topic)") 87 | // simulates that this takes some time 88 | Thread.sleep(1_000) 89 | topic match 90 | case "stock market" | "space" | 91 | "genome" => 92 | Right: 93 | s"detailed history of $topic" 94 | 95 | case "barn" => 96 | Left: 97 | NoWikiArticle() 98 | 99 | import scala.concurrent.Future 100 | 101 | case class HeadlineNotAvailable() 102 | 103 | val getHeadlineZ = 104 | ZIO 105 | .from: 106 | getHeadline() 107 | .orElseFail: 108 | HeadlineNotAvailable() 109 | 110 | object App0 extends helpers.ZIOAppDebug: 111 | def run = 112 | Successful.simulate: 113 | getHeadlineZ 114 | // Network - Getting headline 115 | // Result: stock market rising! 116 | 117 | 118 | object App1 extends helpers.ZIOAppDebug: 119 | def run = 120 | HeadlineError.simulate: 121 | getHeadlineZ 122 | // Network - Getting headline 123 | // Error: HeadlineNotAvailable() 124 | 125 | 126 | val result: Option[String] = 127 | findTopicOfInterest: 128 | "a boring headline" 129 | 130 | case class NoInterestingTopic( 131 | headline: String 132 | ) 133 | 134 | def topicOfInterestZ(headline: String) = 135 | ZIO 136 | .from: 137 | findTopicOfInterest: 138 | headline 139 | .orElseFail: 140 | NoInterestingTopic(headline) 141 | 142 | object App2 extends helpers.ZIOAppDebug: 143 | def run = 144 | topicOfInterestZ: 145 | "stock market rising!" 146 | // Analytics - Scanning for topic 147 | // Analytics - topic: Some(stock market) 148 | // Result: stock market 149 | 150 | 151 | object App3 extends helpers.ZIOAppDebug: 152 | def run = 153 | topicOfInterestZ: 154 | "boring and inane" 155 | // Analytics - Scanning for topic 156 | // Analytics - topic: None 157 | // Error: NoInterestingTopic(boring and inane) 158 | 159 | 160 | def wikiArticleZ(topic: String) = 161 | ZIO.from: 162 | wikiArticle: 163 | topic 164 | 165 | object App4 extends helpers.ZIOAppDebug: 166 | def run = 167 | wikiArticleZ: 168 | "stock market" 169 | // Wiki - articleFor(stock market) 170 | // Result: detailed history of stock market 171 | 172 | 173 | object App5 extends helpers.ZIOAppDebug: 174 | def run = 175 | wikiArticleZ: 176 | "barn" 177 | // Wiki - articleFor(barn) 178 | // Error: NoWikiArticle() 179 | 180 | 181 | import scala.util.Try 182 | 183 | trait File extends AutoCloseable: 184 | def contains(searchTerm: String): Boolean 185 | def write(entry: String): Try[String] 186 | def summaryFor(searchTerm: String): String 187 | def content(): String 188 | 189 | def sameContents( 190 | files: List[File] 191 | ): Boolean = 192 | println: 193 | "side-effect print: comparing content" 194 | 195 | files 196 | .tail 197 | .forall( 198 | _.content() == files.head.content() 199 | ) 200 | 201 | def openFile(path: String) = 202 | new File: 203 | var contents: List[String] = 204 | List("Medical Breakthrough!") 205 | println(s"File - OPEN: $path") 206 | 207 | override def content() = 208 | path match 209 | case "file1" | "file2" | "file3" | 210 | "summaries" => 211 | "hot dog" 212 | case _ => 213 | "not hot dog" 214 | 215 | override def close = 216 | println(s"File - CLOSE: $path") 217 | 218 | override def contains( 219 | searchTerm: String 220 | ): Boolean = 221 | 222 | val result = 223 | searchTerm match 224 | case "wheel" | "unicode" => 225 | true 226 | case _ => 227 | false 228 | println: 229 | s"File - contains($searchTerm) => $result" 230 | result 231 | 232 | override def summaryFor( 233 | searchTerm: String 234 | ): String = 235 | println( 236 | s"File - summaryFor($searchTerm)" 237 | ) 238 | if searchTerm == "unicode" then 239 | println("File - * Threw Exception *") 240 | throw Exception(s"FileSystem error") 241 | else if searchTerm == "stock market" 242 | then 243 | "stock markets are neat" 244 | else if searchTerm == "space" then 245 | "space is huge" 246 | else 247 | ??? 248 | 249 | override def write( 250 | entry: String 251 | ): Try[String] = 252 | if entry.contains("genome") then 253 | println("File - disk full!") 254 | Try( 255 | throw new Exception( 256 | "Disk is full!" 257 | ) 258 | ) 259 | else 260 | println("File - write: " + entry) 261 | contents = entry :: contents 262 | Try(entry) 263 | 264 | def openFileZ(path: String) = 265 | ZIO.fromAutoCloseable: 266 | ZIO.succeed: 267 | openFile(path) 268 | 269 | object App6 extends helpers.ZIOAppDebug: 270 | def run = 271 | defer: 272 | val file = openFileZ("file1").run 273 | file.contains: 274 | "topicOfInterest" 275 | // File - OPEN: file1 276 | // File - contains(topicOfInterest) => false 277 | // File - CLOSE: file1 278 | // Result: false 279 | 280 | 281 | object App7 extends helpers.ZIOAppDebug: 282 | import scala.util.Using 283 | 284 | def run = 285 | defer: 286 | Using(openFile("file1")): 287 | file1 => 288 | Using(openFile("file2")): 289 | file2 => 290 | Using(openFile("file3")): 291 | file3 => 292 | sameContents: 293 | List(file1, file2, file3) 294 | .get 295 | .get 296 | .get 297 | // File - OPEN: file1 298 | // File - OPEN: file2 299 | // File - OPEN: file3 300 | // side-effect print: comparing content 301 | // File - CLOSE: file3 302 | // File - CLOSE: file2 303 | // File - CLOSE: file1 304 | // Result: true 305 | 306 | 307 | object App8 extends helpers.ZIOAppDebug: 308 | def run = 309 | defer: 310 | val file1 = openFileZ("file1").run 311 | val file2 = openFileZ("file2").run 312 | val file3 = openFileZ("file3").run 313 | sameContents: 314 | List(file1, file2, file3) 315 | // File - OPEN: file1 316 | // File - OPEN: file2 317 | // File - OPEN: file3 318 | // side-effect print: comparing content 319 | // File - CLOSE: file3 320 | // File - CLOSE: file2 321 | // File - CLOSE: file1 322 | // Result: true 323 | 324 | 325 | object App9 extends helpers.ZIOAppDebug: 326 | def run = 327 | defer: 328 | val fileNames = 329 | List("file1", "file2", "file3") 330 | 331 | val files = 332 | ZIO.foreach(fileNames)(openFileZ).run 333 | 334 | sameContents(files) 335 | // File - OPEN: file1 336 | // File - OPEN: file2 337 | // File - OPEN: file3 338 | // side-effect print: comparing content 339 | // File - CLOSE: file3 340 | // File - CLOSE: file2 341 | // File - CLOSE: file1 342 | // Result: true 343 | 344 | 345 | case class FileWriteFailure() 346 | 347 | def writeToFileZ( 348 | file: File, 349 | content: String, 350 | ) = 351 | ZIO 352 | .from: 353 | file.write: 354 | content 355 | .orElseFail: 356 | FileWriteFailure() 357 | 358 | object App10 extends helpers.ZIOAppDebug: 359 | def run = 360 | defer: 361 | val file = openFileZ("file1").run 362 | writeToFileZ(file, "New data").run 363 | // File - OPEN: file1 364 | // File - write: New data 365 | // File - CLOSE: file1 366 | // Result: New data 367 | 368 | 369 | object App11 extends helpers.ZIOAppDebug: 370 | def run = 371 | defer: 372 | openFile("file1").summaryFor("space") 373 | // File - OPEN: file1 374 | // File - summaryFor(space) 375 | // Result: space is huge 376 | 377 | 378 | object App12 extends helpers.ZIOAppDebug: 379 | def run = 380 | defer: 381 | openFile("file1").summaryFor("unicode") 382 | // File - OPEN: file1 383 | // File - summaryFor(unicode) 384 | // File - * Threw Exception * 385 | // Defect: java.lang.Exception: FileSystem error 386 | 387 | 388 | case class FileReadFailure(topic: String) 389 | 390 | def summaryForZ(file: File, topic: String) = 391 | ZIO 392 | .attempt: 393 | file.summaryFor(topic) 394 | .orElseFail: 395 | FileReadFailure(topic) 396 | 397 | def summarize(article: String): String = 398 | println(s"AI - summarize - start") 399 | // Represents the AI taking a long time to 400 | // summarize the content 401 | if article.contains("space") then 402 | println("AI - taking a long time") 403 | Thread.sleep(5_000) 404 | 405 | println(s"AI - summarize - end") 406 | if article.contains("stock market") then 407 | s"market is moving" 408 | else if article.contains("genome") then 409 | "The human genome is huge!" 410 | else if article.contains("long article") 411 | then 412 | "short summary" 413 | else 414 | ??? 415 | 416 | object App13 extends helpers.ZIOAppDebug: 417 | def run = 418 | defer: 419 | summarize("long article") 420 | // AI - summarize - start 421 | // AI - summarize - end 422 | // Result: short summary 423 | 424 | 425 | import java.util.concurrent.TimeoutException 426 | 427 | def summarizeZ(article: String) = 428 | ZIO 429 | .attemptBlockingInterrupt: 430 | summarize(article) 431 | .onInterrupt: 432 | ZIO.debug("AI **INTERRUPTED**") 433 | .timeoutFail(TimeoutException()): 434 | 4.seconds 435 | 436 | object App14 extends helpers.ZIOAppDebug: 437 | def run = 438 | summarizeZ("long article") 439 | // AI - summarize - start 440 | // AI - summarize - end 441 | // Result: short summary 442 | 443 | 444 | object App15 extends helpers.ZIOAppDebug: 445 | def run = 446 | summarizeZ("space") 447 | // AI - summarize - start 448 | // AI - taking a long time 449 | // AI **INTERRUPTED** 450 | // Error: java.util.concurrent.TimeoutException 451 | 452 | 453 | val researchHeadline = 454 | defer: 455 | val headline: String = getHeadlineZ.run 456 | 457 | val topic: String = 458 | topicOfInterestZ(headline).run 459 | 460 | val summaryFile: File = 461 | openFileZ("summaries").run 462 | 463 | if summaryFile.contains(topic) then 464 | summaryForZ(summaryFile, topic).run 465 | else 466 | val wikiArticle: String = 467 | wikiArticleZ(topic).run 468 | 469 | val summary: String = 470 | summarizeZ(wikiArticle).run 471 | 472 | writeToFileZ(summaryFile, summary).run 473 | 474 | summary 475 | 476 | object App16 extends helpers.ZIOAppDebug: 477 | def run = 478 | HeadlineError.simulate: 479 | researchHeadline 480 | // Network - Getting headline 481 | // Error: HeadlineNotAvailable() 482 | 483 | 484 | object App17 extends helpers.ZIOAppDebug: 485 | def run = 486 | BoringTopic.simulate: 487 | researchHeadline 488 | // Network - Getting headline 489 | // Analytics - Scanning for topic 490 | // Analytics - topic: None 491 | // Error: NoInterestingTopic(boring content) 492 | 493 | 494 | object App18 extends helpers.ZIOAppDebug: 495 | def run = 496 | FileSystemError.simulate: 497 | researchHeadline 498 | // Network - Getting headline 499 | // Analytics - Scanning for topic 500 | // Analytics - topic: Some(unicode) 501 | // File - OPEN: summaries 502 | // File - contains(unicode) => true 503 | // File - summaryFor(unicode) 504 | // File - * Threw Exception * 505 | // File - CLOSE: summaries 506 | // Error: FileReadFailure(unicode) 507 | 508 | 509 | object App19 extends helpers.ZIOAppDebug: 510 | def run = 511 | WikiSystemError.simulate: 512 | researchHeadline 513 | // Network - Getting headline 514 | // Analytics - Scanning for topic 515 | // Analytics - topic: Some(barn) 516 | // File - OPEN: summaries 517 | // File - contains(barn) => false 518 | // Wiki - articleFor(barn) 519 | // File - CLOSE: summaries 520 | // Error: NoWikiArticle() 521 | 522 | 523 | object App20 extends helpers.ZIOAppDebug: 524 | def run = 525 | AISlow.simulate: 526 | researchHeadline 527 | // Network - Getting headline 528 | // Analytics - Scanning for topic 529 | // Analytics - topic: Some(space) 530 | // File - OPEN: summaries 531 | // File - contains(space) => false 532 | // Wiki - articleFor(space) 533 | // AI - summarize - start 534 | // AI - taking a long time 535 | // AI **INTERRUPTED** 536 | // File - CLOSE: summaries 537 | // Error: java.util.concurrent.TimeoutException 538 | 539 | 540 | object App21 extends helpers.ZIOAppDebug: 541 | def run = 542 | DiskFull.simulate: 543 | researchHeadline 544 | // Network - Getting headline 545 | // Analytics - Scanning for topic 546 | // Analytics - topic: Some(genome) 547 | // File - OPEN: summaries 548 | // File - contains(genome) => false 549 | // Wiki - articleFor(genome) 550 | // AI - summarize - start 551 | // AI - summarize - end 552 | // File - disk full! 553 | // File - CLOSE: summaries 554 | // Error: FileWriteFailure() 555 | 556 | 557 | object App22 extends helpers.ZIOAppDebug: 558 | def run = 559 | Successful.simulate: 560 | researchHeadline 561 | // Network - Getting headline 562 | // Analytics - Scanning for topic 563 | // Analytics - topic: Some(stock market) 564 | // File - OPEN: summaries 565 | // File - contains(stock market) => false 566 | // Wiki - articleFor(stock market) 567 | // AI - summarize - start 568 | // AI - summarize - end 569 | // File - write: market is moving 570 | // File - CLOSE: summaries 571 | // Result: market is moving 572 | 573 | 574 | val quickResearch = 575 | researchHeadline 576 | .timeoutFail("strict timeout"): 577 | 100.milliseconds 578 | 579 | object App23 extends helpers.ZIOAppDebug: 580 | def run = 581 | Successful.simulate: 582 | quickResearch 583 | // Network - Getting headline 584 | // Analytics - Scanning for topic 585 | // Analytics - topic: Some(stock market) 586 | // File - OPEN: summaries 587 | // File - contains(stock market) => false 588 | // Wiki - articleFor(stock market) 589 | // File - CLOSE: summaries 590 | // Error: strict timeout 591 | 592 | 593 | val daily = 594 | Successful.simulate: 595 | quickResearch.repeat: 596 | Schedule.spaced(24.hours) -------------------------------------------------------------------------------- /src/main/scala/Chapter09_Resilience.scala: -------------------------------------------------------------------------------- 1 | package Chapter09_Resilience 2 | 3 | import zio.* 4 | import zio.direct.* 5 | 6 | import zio.{ZIO, ZLayer} 7 | // This utilizes: https://zio.dev/zio-cache/ 8 | // These types have good documentation and can be ctrl-click'ed into 9 | import zio.cache.{Cache, Lookup} 10 | 11 | import java.nio.file.{Path, Paths} 12 | 13 | case class FSLive(requests: Ref[Int]) 14 | extends CloudStorage: 15 | def retrieve( 16 | name: String 17 | ): ZIO[Any, Nothing, FileContents] = 18 | defer: 19 | requests.update(_ + 1).run 20 | ZIO.sleep(10.millis).run 21 | FSLive.hardcodedContents 22 | 23 | val invoice: ZIO[Any, Nothing, String] = 24 | defer: 25 | val count = requests.get.run 26 | "Amount owed: $" + count 27 | 28 | object FSLive: 29 | val hardcodedContents = 30 | FileContents( 31 | List("viralImage1", "viralImage2") 32 | ) 33 | 34 | case class FileContents( 35 | contents: List[String] 36 | ) 37 | 38 | trait CloudStorage: 39 | def retrieve( 40 | name: String 41 | ): ZIO[Any, Nothing, FileContents] 42 | val invoice: ZIO[Any, Nothing, String] 43 | 44 | object CloudStorage: 45 | val live = 46 | ZLayer.fromZIO: 47 | defer: 48 | FSLive(Ref.make(0).run) 49 | 50 | case class PopularService( 51 | retrieveContents: String => ZIO[ 52 | Any, 53 | Nothing, 54 | FileContents, 55 | ] 56 | ): 57 | def retrieve(name: String) = 58 | retrieveContents(name) 59 | 60 | val thunderingHerds = 61 | defer: 62 | val popularService = 63 | ZIO.service[PopularService].run 64 | 65 | val memes = 66 | List.fill(100): 67 | popularService.retrieve: 68 | "Awesome Memes" 69 | 70 | ZIO.collectAllPar(memes).run 71 | 72 | ZIO 73 | .serviceWithZIO[CloudStorage]: 74 | storage => storage.invoice 75 | .run 76 | 77 | val makePopularService = 78 | defer: 79 | val storage = 80 | ZIO.service[CloudStorage].run 81 | 82 | PopularService(storage.retrieve) 83 | 84 | object App0 extends helpers.ZIOAppDebug: 85 | def run = 86 | thunderingHerds.provide( 87 | CloudStorage.live, 88 | ZLayer.fromZIO(makePopularService), 89 | ) 90 | // Result: Amount owed: $100 91 | 92 | 93 | import zio.cache.Cache 94 | 95 | val makeCachedPopularService = 96 | defer: 97 | val storage = 98 | ZIO.service[CloudStorage].run 99 | 100 | val cache = 101 | Cache 102 | .make( 103 | capacity = 1, 104 | timeToLive = Duration.Infinity, 105 | lookup = Lookup(storage.retrieve), 106 | ) 107 | .run 108 | 109 | PopularService(cache.get) 110 | 111 | object App1 extends helpers.ZIOAppDebug: 112 | def run = 113 | thunderingHerds.provide( 114 | CloudStorage.live, 115 | ZLayer.fromZIO: 116 | makeCachedPopularService, 117 | ) 118 | // Result: Amount owed: $1 119 | 120 | 121 | import java.time.Instant 122 | 123 | def expensiveCall( 124 | globalStart: Instant, 125 | caller: String, 126 | ) = 127 | defer: 128 | val now = Clock.instant.run 129 | 130 | val seconds = 131 | java 132 | .time 133 | .Duration 134 | .between(globalStart, now) 135 | .getSeconds 136 | 137 | println: 138 | s"$caller request @ ${seconds}s" 139 | 140 | ZIO.sleep(30.millis).run 141 | 142 | object App2 extends helpers.ZIOAppDebug: 143 | def run = 144 | defer: 145 | val startTime = Clock.instant.run 146 | expensiveCall(startTime, "User") 147 | .repeatN(2) 148 | .run 149 | // User request @ 0s 150 | // User request @ 0s 151 | // User request @ 0s 152 | 153 | 154 | import nl.vroste.rezilience.RateLimiter 155 | 156 | val makeRateLimiter = 157 | RateLimiter 158 | .make(max = 1, interval = 1.second) 159 | 160 | object App3 extends helpers.ZIOAppDebug: 161 | def run = 162 | defer: 163 | val startTime = Clock.instant.run 164 | val rateLimiter = makeRateLimiter.run 165 | rateLimiter: 166 | expensiveCall(startTime, "User") 167 | .repeatN(2) 168 | .run 169 | // User request @ 0s 170 | // User request @ 1s 171 | // User request @ 2s 172 | 173 | 174 | object App4 extends helpers.ZIOAppDebug: 175 | def run = 176 | defer: 177 | val rateLimiter = makeRateLimiter.run 178 | val users = 179 | List("Bill ", "Bruce", "James") 180 | val startTime = Clock.instant.run 181 | ZIO 182 | .foreachPar(users): 183 | user => 184 | rateLimiter: 185 | expensiveCall(startTime, user) 186 | .repeatN(2) 187 | .as("All requests succeeded") 188 | .run 189 | // Bill request @ 0s 190 | // James request @ 1s 191 | // Bruce request @ 2s 192 | // Bill request @ 3s 193 | // James request @ 4s 194 | // Bruce request @ 5s 195 | // Bill request @ 6s 196 | // James request @ 7s 197 | // Bruce request @ 8s 198 | // Result: All requests succeeded 199 | 200 | 201 | trait DelicateResource: 202 | val request: ZIO[Any, String, List[Char]] 203 | 204 | case class Live( 205 | requestIdQueue: Queue[Char], 206 | currentRequestsRef: Ref.Synchronized[ 207 | (Option[Char], List[Char]) 208 | ], 209 | ) extends DelicateResource: 210 | val request = 211 | defer: 212 | val ( 213 | thisRequestOption, 214 | currentRequests, 215 | ) = 216 | currentRequestsRef 217 | .updateAndGetZIO: 218 | (_, currentRequests) => 219 | defer: 220 | // we do this here so we get 221 | // sequential request ids 222 | val res = 223 | requestIdQueue.take.run 224 | 225 | if currentRequests.size > 3 226 | then 227 | ZIO 228 | .fail( 229 | "Crashed the server!!" 230 | ) 231 | .run 232 | else 233 | val updatedRequests = 234 | currentRequests :+ res 235 | ZIO 236 | .debug( 237 | s"Current requests: $updatedRequests" 238 | ) 239 | .run 240 | ( 241 | Some(res), 242 | updatedRequests, 243 | ) 244 | .run 245 | 246 | val thisRequest = thisRequestOption.get 247 | ZIO 248 | .sleep: 249 | thisRequest.intValue.millis * 10 250 | .run 251 | 252 | currentRequestsRef 253 | .update: 254 | (_, currentRequests) => 255 | ( 256 | thisRequestOption, 257 | currentRequests 258 | .filterNot(_ == thisRequest), 259 | ) 260 | .run 261 | 262 | currentRequests 263 | end Live 264 | 265 | object DelicateResource: 266 | val live = 267 | ZLayer.fromZIO: 268 | defer: 269 | ZIO 270 | .debug: 271 | "Delicate Resource constructed." 272 | .run 273 | ZIO 274 | .debug: 275 | "Do not make more than 3 concurrent requests!" 276 | .run 277 | 278 | val atoz = 'A' to 'Z' 279 | val orderingQueue = 280 | Queue.unbounded[Char].run 281 | orderingQueue.offerAll(atoz).run 282 | 283 | val currentRequests = 284 | Ref 285 | .Synchronized 286 | .make( 287 | Option.empty[Char], 288 | List.empty[Char], 289 | ) 290 | .run 291 | 292 | Live(orderingQueue, currentRequests) 293 | end DelicateResource 294 | 295 | object App5 extends helpers.ZIOAppDebug: 296 | def run = 297 | defer: 298 | val delicateResource = 299 | ZIO.service[DelicateResource].run 300 | ZIO 301 | .foreachPar(1 to 10): 302 | _ => delicateResource.request 303 | .as("All Requests Succeeded!") 304 | .run 305 | .provide(DelicateResource.live) 306 | // Delicate Resource constructed. 307 | // Do not make more than 3 concurrent requests! 308 | // Current requests: List(A) 309 | // Current requests: List(A, B) 310 | // Current requests: List(A, B, C) 311 | // Current requests: List(A, B, C, D) 312 | // Error: Crashed the server!! 313 | 314 | 315 | import nl.vroste.rezilience.Bulkhead 316 | 317 | val makeBulkhead = 318 | Bulkhead.make(maxInFlightCalls = 3) 319 | 320 | object App6 extends helpers.ZIOAppDebug: 321 | def run = 322 | ZIO 323 | .scoped: 324 | defer: 325 | val bulkhead = makeBulkhead.run 326 | val delicateResource = 327 | ZIO.service[DelicateResource].run 328 | ZIO 329 | .foreachPar(1 to 10): 330 | _ => 331 | bulkhead: 332 | delicateResource.request 333 | .as("All Requests Succeeded") 334 | .run 335 | .provide(DelicateResource.live) 336 | // Delicate Resource constructed. 337 | // Do not make more than 3 concurrent requests! 338 | // Current requests: List(A) 339 | // Current requests: List(A, B) 340 | // Current requests: List(A, B, C) 341 | // Current requests: List(B, C, D) 342 | // Current requests: List(C, D, E) 343 | // Current requests: List(D, E, F) 344 | // Current requests: List(E, F, G) 345 | // Current requests: List(F, G, H) 346 | // Current requests: List(G, H, I) 347 | // Current requests: List(H, I, J) 348 | // Result: All Requests Succeeded 349 | 350 | 351 | import zio.Ref 352 | 353 | import java.time.Instant 354 | import scala.concurrent.TimeoutException 355 | 356 | // Invisible mdoc fences 357 | import zio.Runtime.default.unsafe 358 | 359 | val timeSensitiveValue = 360 | Unsafe.unsafe( 361 | (u: Unsafe) => 362 | given Unsafe = 363 | u 364 | unsafe 365 | .run( 366 | scheduledValues( 367 | (1_100.millis, true), 368 | (4_100.millis, false), 369 | (5_000.millis, true), 370 | ) 371 | ) 372 | .getOrThrowFiberFailure() 373 | ) 374 | 375 | def externalService( 376 | callsMade: Ref[Int], 377 | failures: Ref[Int], 378 | ) = 379 | defer: 380 | callsMade.update(_ + 1).run 381 | val b = timeSensitiveValue.run 382 | if b then 383 | ZIO.unit.run 384 | else 385 | failures.update(_ + 1).run 386 | ZIO.fail(()).run 387 | 388 | object InstantOps: 389 | extension (i: Instant) 390 | def plusZ( 391 | duration: zio.Duration 392 | ): Instant = 393 | i.plus(duration.asJava) 394 | 395 | import InstantOps.* 396 | 397 | /* Goal: If I accessed this from: 398 | * 0-1 seconds, I would get "First Value" 1-4 399 | * seconds, I would get "Second Value" 4-14 400 | * seconds, I would get "Third Value" 14+ 401 | * seconds, it would fail */ 402 | 403 | def scheduledValues[A]( 404 | value: (Duration, A), 405 | values: (Duration, A)* 406 | ): ZIO[ 407 | Any, // construction time 408 | Nothing, 409 | ZIO[ 410 | Any, // access time 411 | TimeoutException, 412 | A, 413 | ], 414 | ] = 415 | defer: 416 | val startTime = Clock.instant.run 417 | val timeTable = 418 | createTimeTableX( 419 | startTime, 420 | value, 421 | values* // Yay Scala3 :) 422 | ) 423 | accessX(timeTable) 424 | 425 | // make this function more obvious 426 | private def createTimeTableX[A]( 427 | startTime: Instant, 428 | value: (Duration, A), 429 | values: (Duration, A)* 430 | ): Seq[ExpiringValue[A]] = 431 | values.scanLeft( 432 | ExpiringValue( 433 | startTime.plusZ(value._1), 434 | value._2, 435 | ) 436 | ): 437 | case ( 438 | ExpiringValue(elapsed, _), 439 | (duration, value), 440 | ) => 441 | ExpiringValue( 442 | elapsed.plusZ(duration), 443 | value, 444 | ) 445 | 446 | /** Input: (1 minute, "value1") (2 minute, 447 | * "value2") 448 | * 449 | * Runtime: Zero value: (8:00 + 1 minute, 450 | * "value1") 451 | * 452 | * case ((8:01, _) , (2.minutes, "value2")) 453 | * \=> (8:01 + 2.minutes, "value2") 454 | * 455 | * Output: ( ("8:01", "value1"), ("8:03", 456 | * "value2") ) 457 | */ 458 | private def accessX[A]( 459 | timeTable: Seq[ExpiringValue[A]] 460 | ): ZIO[Any, TimeoutException, A] = 461 | defer: 462 | val now = Clock.instant.run 463 | ZIO 464 | .getOrFailWith( 465 | new TimeoutException("TOO LATE") 466 | ): 467 | timeTable 468 | .find( 469 | _.expirationTime.isAfter(now) 470 | ) 471 | .map(_.value) 472 | .run 473 | 474 | private case class ExpiringValue[A]( 475 | expirationTime: Instant, 476 | value: A, 477 | ) 478 | 479 | val rapidly = 480 | Schedule.recurs(140) && 481 | Schedule.spaced(50.millis) 482 | 483 | object App7 extends helpers.ZIOAppDebug: 484 | def run = 485 | defer: 486 | val callsMade = Ref.make(0).run 487 | val failures = Ref.make(0).run 488 | externalService(callsMade, failures) 489 | .ignore 490 | .repeat(rapidly) 491 | .run 492 | 493 | val made = callsMade.get.run 494 | val failed = failures.get.run 495 | 496 | s""" 497 | Total Submitted: $made 498 | Failed: $failed 499 | """.stripMargin 500 | // Result: 501 | // Total Submitted: 141 502 | // Failed: 81 503 | 504 | 505 | import nl.vroste.rezilience.{ 506 | CircuitBreaker, 507 | TrippingStrategy, 508 | Retry, 509 | } 510 | import TrippingStrategy.failureCount 511 | 512 | val circuitBreakerZ = 513 | CircuitBreaker.make( 514 | trippingStrategy = failureCount(2), 515 | resetPolicy = Retry.Schedules.common(), 516 | ) 517 | 518 | object App8 extends helpers.ZIOAppDebug: 519 | import CircuitBreaker.CircuitBreakerOpen 520 | 521 | def run = 522 | defer: 523 | val circuitBreaker = circuitBreakerZ.run 524 | val callsMade = Ref.make[Int](0).run 525 | val callsPrevented = Ref.make[Int](0).run 526 | val failures = Ref.make[Int](0).run 527 | 528 | val protectedCall = 529 | circuitBreaker: 530 | externalService(callsMade, failures) 531 | .catchSome: 532 | case CircuitBreakerOpen => 533 | callsPrevented.update(_ + 1) 534 | case other => 535 | ZIO.unit 536 | 537 | protectedCall.ignore.repeat(rapidly).run 538 | 539 | val prevented = callsPrevented.get.run 540 | val made = callsMade.get.run 541 | val failed = failures.get.run 542 | s""" 543 | Total Submitted: $made 544 | Failed: $failed 545 | Prevented: $prevented 546 | """.stripMargin 547 | // Result: 548 | // Total Submitted: 67 549 | // Failed: 0 550 | // Prevented: 74 551 | 552 | 553 | val intermittentSlowResponse = 554 | defer: 555 | if Random.nextIntBounded(1_000).run == 0 556 | then 557 | ZIO.sleep(3.second).run 558 | 559 | def lotsOfRequests( 560 | totalRequests: Int, 561 | timeout: Duration, 562 | request: ZIO[Any, Throwable, Unit], 563 | ): ZIO[Any, Nothing, Int] = 564 | defer: 565 | val successes = 566 | ZIO 567 | // Evaluate and run each effect in 568 | // the structure in parallel, and 569 | // collect discarding failed ones. 570 | .collectAllSuccessesPar: 571 | List.fill(totalRequests): 572 | request 573 | .timeoutFail("took too long"): 574 | timeout 575 | .run 576 | 577 | totalRequests - successes.length 578 | 579 | object App9 extends helpers.ZIOAppDebug: 580 | def run = 581 | defer: 582 | val numFailed = 583 | lotsOfRequests( 584 | 50_000, 585 | 1.second, 586 | intermittentSlowResponse, 587 | ).run 588 | s"$numFailed requests timed out" 589 | // Result: 46 requests timed out 590 | 591 | 592 | val hedged = 593 | intermittentSlowResponse.race: 594 | intermittentSlowResponse.delay: 595 | 20.millis 596 | 597 | object App10 extends helpers.ZIOAppDebug: 598 | def run = 599 | defer: 600 | val numFailed = 601 | lotsOfRequests( 602 | 50_000, 603 | 1.second, 604 | hedged, 605 | ).run 606 | 607 | s"$numFailed requests timed out" 608 | // Result: 0 requests timed out 609 | 610 | 611 | val attemptsR = 612 | Unsafe.unsafe: 613 | implicit unsafe => 614 | Runtime 615 | .default 616 | .unsafe 617 | .run(Ref.make(0)) 618 | .getOrThrowFiberFailure() 619 | 620 | def spottyLogic = 621 | defer: 622 | val attemptsCur = 623 | attemptsR.getAndUpdate(_ + 1).run 624 | if ZIO.attempt(attemptsCur).run == 3 then 625 | ZIO.debug("Success!").run 626 | ZIO.succeed(true).run 627 | else 628 | ZIO.debug("Failed!").run 629 | ZIO.succeed(false).run -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution 4.0 International Public License 58 | 59 | By exercising the Licensed Rights (defined below), You accept and agree 60 | to be bound by the terms and conditions of this Creative Commons 61 | Attribution 4.0 International Public License ("Public License"). To the 62 | extent this Public License may be interpreted as a contract, You are 63 | granted the Licensed Rights in consideration of Your acceptance of 64 | these terms and conditions, and the Licensor grants You such rights in 65 | consideration of benefits the Licensor receives from making the 66 | Licensed Material available under these terms and conditions. 67 | 68 | 69 | Section 1 -- Definitions. 70 | 71 | a. Adapted Material means material subject to Copyright and Similar 72 | Rights that is derived from or based upon the Licensed Material 73 | and in which the Licensed Material is translated, altered, 74 | arranged, transformed, or otherwise modified in a manner requiring 75 | permission under the Copyright and Similar Rights held by the 76 | Licensor. For purposes of this Public License, where the Licensed 77 | Material is a musical work, performance, or sound recording, 78 | Adapted Material is always produced where the Licensed Material is 79 | synched in timed relation with a moving image. 80 | 81 | b. Adapter's License means the license You apply to Your Copyright 82 | and Similar Rights in Your contributions to Adapted Material in 83 | accordance with the terms and conditions of this Public License. 84 | 85 | c. Copyright and Similar Rights means copyright and/or similar rights 86 | closely related to copyright including, without limitation, 87 | performance, broadcast, sound recording, and Sui Generis Database 88 | Rights, without regard to how the rights are labeled or 89 | categorized. For purposes of this Public License, the rights 90 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 91 | Rights. 92 | 93 | d. Effective Technological Measures means those measures that, in the 94 | absence of proper authority, may not be circumvented under laws 95 | fulfilling obligations under Article 11 of the WIPO Copyright 96 | Treaty adopted on December 20, 1996, and/or similar international 97 | agreements. 98 | 99 | e. Exceptions and Limitations means fair use, fair dealing, and/or 100 | any other exception or limitation to Copyright and Similar Rights 101 | that applies to Your use of the Licensed Material. 102 | 103 | f. Licensed Material means the artistic or literary work, database, 104 | or other material to which the Licensor applied this Public 105 | License. 106 | 107 | g. Licensed Rights means the rights granted to You subject to the 108 | terms and conditions of this Public License, which are limited to 109 | all Copyright and Similar Rights that apply to Your use of the 110 | Licensed Material and that the Licensor has authority to license. 111 | 112 | h. Licensor means the individual(s) or entity(ies) granting rights 113 | under this Public License. 114 | 115 | i. Share means to provide material to the public by any means or 116 | process that requires permission under the Licensed Rights, such 117 | as reproduction, public display, public performance, distribution, 118 | dissemination, communication, or importation, and to make material 119 | available to the public including in ways that members of the 120 | public may access the material from a place and at a time 121 | individually chosen by them. 122 | 123 | j. Sui Generis Database Rights means rights other than copyright 124 | resulting from Directive 96/9/EC of the European Parliament and of 125 | the Council of 11 March 1996 on the legal protection of databases, 126 | as amended and/or succeeded, as well as other essentially 127 | equivalent rights anywhere in the world. 128 | 129 | k. You means the individual or entity exercising the Licensed Rights 130 | under this Public License. Your has a corresponding meaning. 131 | 132 | 133 | Section 2 -- Scope. 134 | 135 | a. License grant. 136 | 137 | 1. Subject to the terms and conditions of this Public License, 138 | the Licensor hereby grants You a worldwide, royalty-free, 139 | non-sublicensable, non-exclusive, irrevocable license to 140 | exercise the Licensed Rights in the Licensed Material to: 141 | 142 | a. reproduce and Share the Licensed Material, in whole or 143 | in part; and 144 | 145 | b. produce, reproduce, and Share Adapted Material. 146 | 147 | 2. Exceptions and Limitations. For the avoidance of doubt, where 148 | Exceptions and Limitations apply to Your use, this Public 149 | License does not apply, and You do not need to comply with 150 | its terms and conditions. 151 | 152 | 3. Term. The term of this Public License is specified in Section 153 | 6(a). 154 | 155 | 4. Media and formats; technical modifications allowed. The 156 | Licensor authorizes You to exercise the Licensed Rights in 157 | all media and formats whether now known or hereafter created, 158 | and to make technical modifications necessary to do so. The 159 | Licensor waives and/or agrees not to assert any right or 160 | authority to forbid You from making technical modifications 161 | necessary to exercise the Licensed Rights, including 162 | technical modifications necessary to circumvent Effective 163 | Technological Measures. For purposes of this Public License, 164 | simply making modifications authorized by this Section 2(a) 165 | (4) never produces Adapted Material. 166 | 167 | 5. Downstream recipients. 168 | 169 | a. Offer from the Licensor -- Licensed Material. Every 170 | recipient of the Licensed Material automatically 171 | receives an offer from the Licensor to exercise the 172 | Licensed Rights under the terms and conditions of this 173 | Public License. 174 | 175 | b. No downstream restrictions. You may not offer or impose 176 | any additional or different terms or conditions on, or 177 | apply any Effective Technological Measures to, the 178 | Licensed Material if doing so restricts exercise of the 179 | Licensed Rights by any recipient of the Licensed 180 | Material. 181 | 182 | 6. No endorsement. Nothing in this Public License constitutes or 183 | may be construed as permission to assert or imply that You 184 | are, or that Your use of the Licensed Material is, connected 185 | with, or sponsored, endorsed, or granted official status by, 186 | the Licensor or others designated to receive attribution as 187 | provided in Section 3(a)(1)(A)(i). 188 | 189 | b. Other rights. 190 | 191 | 1. Moral rights, such as the right of integrity, are not 192 | licensed under this Public License, nor are publicity, 193 | privacy, and/or other similar personality rights; however, to 194 | the extent possible, the Licensor waives and/or agrees not to 195 | assert any such rights held by the Licensor to the limited 196 | extent necessary to allow You to exercise the Licensed 197 | Rights, but not otherwise. 198 | 199 | 2. Patent and trademark rights are not licensed under this 200 | Public License. 201 | 202 | 3. To the extent possible, the Licensor waives any right to 203 | collect royalties from You for the exercise of the Licensed 204 | Rights, whether directly or through a collecting society 205 | under any voluntary or waivable statutory or compulsory 206 | licensing scheme. In all other cases the Licensor expressly 207 | reserves any right to collect such royalties. 208 | 209 | 210 | Section 3 -- License Conditions. 211 | 212 | Your exercise of the Licensed Rights is expressly made subject to the 213 | following conditions. 214 | 215 | a. Attribution. 216 | 217 | 1. If You Share the Licensed Material (including in modified 218 | form), You must: 219 | 220 | a. retain the following if it is supplied by the Licensor 221 | with the Licensed Material: 222 | 223 | i. identification of the creator(s) of the Licensed 224 | Material and any others designated to receive 225 | attribution, in any reasonable manner requested by 226 | the Licensor (including by pseudonym if 227 | designated); 228 | 229 | ii. a copyright notice; 230 | 231 | iii. a notice that refers to this Public License; 232 | 233 | iv. a notice that refers to the disclaimer of 234 | warranties; 235 | 236 | v. a URI or hyperlink to the Licensed Material to the 237 | extent reasonably practicable; 238 | 239 | b. indicate if You modified the Licensed Material and 240 | retain an indication of any previous modifications; and 241 | 242 | c. indicate the Licensed Material is licensed under this 243 | Public License, and include the text of, or the URI or 244 | hyperlink to, this Public License. 245 | 246 | 2. You may satisfy the conditions in Section 3(a)(1) in any 247 | reasonable manner based on the medium, means, and context in 248 | which You Share the Licensed Material. For example, it may be 249 | reasonable to satisfy the conditions by providing a URI or 250 | hyperlink to a resource that includes the required 251 | information. 252 | 253 | 3. If requested by the Licensor, You must remove any of the 254 | information required by Section 3(a)(1)(A) to the extent 255 | reasonably practicable. 256 | 257 | 4. If You Share Adapted Material You produce, the Adapter's 258 | License You apply must not prevent recipients of the Adapted 259 | Material from complying with this Public License. 260 | 261 | 262 | Section 4 -- Sui Generis Database Rights. 263 | 264 | Where the Licensed Rights include Sui Generis Database Rights that 265 | apply to Your use of the Licensed Material: 266 | 267 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 268 | to extract, reuse, reproduce, and Share all or a substantial 269 | portion of the contents of the database; 270 | 271 | b. if You include all or a substantial portion of the database 272 | contents in a database in which You have Sui Generis Database 273 | Rights, then the database in which You have Sui Generis Database 274 | Rights (but not its individual contents) is Adapted Material; and 275 | 276 | c. You must comply with the conditions in Section 3(a) if You Share 277 | all or a substantial portion of the contents of the database. 278 | 279 | For the avoidance of doubt, this Section 4 supplements and does not 280 | replace Your obligations under this Public License where the Licensed 281 | Rights include other Copyright and Similar Rights. 282 | 283 | 284 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 285 | 286 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 287 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 288 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 289 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 290 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 291 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 292 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 293 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 294 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 295 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 296 | 297 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 298 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 299 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 300 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 301 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 302 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 303 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 304 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 305 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 306 | 307 | c. The disclaimer of warranties and limitation of liability provided 308 | above shall be interpreted in a manner that, to the extent 309 | possible, most closely approximates an absolute disclaimer and 310 | waiver of all liability. 311 | 312 | 313 | Section 6 -- Term and Termination. 314 | 315 | a. This Public License applies for the term of the Copyright and 316 | Similar Rights licensed here. However, if You fail to comply with 317 | this Public License, then Your rights under this Public License 318 | terminate automatically. 319 | 320 | b. Where Your right to use the Licensed Material has terminated under 321 | Section 6(a), it reinstates: 322 | 323 | 1. automatically as of the date the violation is cured, provided 324 | it is cured within 30 days of Your discovery of the 325 | violation; or 326 | 327 | 2. upon express reinstatement by the Licensor. 328 | 329 | For the avoidance of doubt, this Section 6(b) does not affect any 330 | right the Licensor may have to seek remedies for Your violations 331 | of this Public License. 332 | 333 | c. For the avoidance of doubt, the Licensor may also offer the 334 | Licensed Material under separate terms or conditions or stop 335 | distributing the Licensed Material at any time; however, doing so 336 | will not terminate this Public License. 337 | 338 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 339 | License. 340 | 341 | 342 | Section 7 -- Other Terms and Conditions. 343 | 344 | a. The Licensor shall not be bound by any additional or different 345 | terms or conditions communicated by You unless expressly agreed. 346 | 347 | b. Any arrangements, understandings, or agreements regarding the 348 | Licensed Material not stated herein are separate from and 349 | independent of the terms and conditions of this Public License. 350 | 351 | 352 | Section 8 -- Interpretation. 353 | 354 | a. For the avoidance of doubt, this Public License does not, and 355 | shall not be interpreted to, reduce, limit, restrict, or impose 356 | conditions on any use of the Licensed Material that could lawfully 357 | be made without permission under this Public License. 358 | 359 | b. To the extent possible, if any provision of this Public License is 360 | deemed unenforceable, it shall be automatically reformed to the 361 | minimum extent necessary to make it enforceable. If the provision 362 | cannot be reformed, it shall be severed from this Public License 363 | without affecting the enforceability of the remaining terms and 364 | conditions. 365 | 366 | c. No term or condition of this Public License will be waived and no 367 | failure to comply consented to unless expressly agreed to by the 368 | Licensor. 369 | 370 | d. Nothing in this Public License constitutes or may be interpreted 371 | as a limitation upon, or waiver of, any privileges and immunities 372 | that apply to the Licensor or You, including from the legal 373 | processes of any jurisdiction or authority. 374 | 375 | 376 | ======================================================================= 377 | 378 | Creative Commons is not a party to its public 379 | licenses. Notwithstanding, Creative Commons may elect to apply one of 380 | its public licenses to material it publishes and in those instances 381 | will be considered the “Licensor.” The text of the Creative Commons 382 | public licenses is dedicated to the public domain under the CC0 Public 383 | Domain Dedication. Except for the limited purpose of indicating that 384 | material is shared under a Creative Commons public license or as 385 | otherwise permitted by the Creative Commons policies published at 386 | creativecommons.org/policies, Creative Commons does not authorize the 387 | use of the trademark "Creative Commons" or any other trademark or logo 388 | of Creative Commons without its prior written consent including, 389 | without limitation, in connection with any unauthorized modifications 390 | to any of its public licenses or any other arrangements, 391 | understandings, or agreements concerning use of licensed material. For 392 | the avoidance of doubt, this paragraph does not form part of the 393 | public licenses. 394 | 395 | Creative Commons may be contacted at creativecommons.org. 396 | -------------------------------------------------------------------------------- /sbt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set +e 4 | declare builtin_sbt_version="1.10.1" 5 | declare -a residual_args 6 | declare -a java_args 7 | declare -a scalac_args 8 | declare -a sbt_commands 9 | declare -a sbt_options 10 | declare -a print_version 11 | declare -a print_sbt_version 12 | declare -a print_sbt_script_version 13 | declare -a shutdownall 14 | declare -a original_args 15 | declare java_cmd=java 16 | declare java_version 17 | declare init_sbt_version=1.10.1 18 | declare sbt_default_mem=1024 19 | declare -r default_sbt_opts="" 20 | declare -r default_java_opts="-Dfile.encoding=UTF-8" 21 | declare sbt_verbose= 22 | declare sbt_debug= 23 | declare build_props_sbt_version= 24 | declare use_sbtn= 25 | declare no_server= 26 | declare sbtn_command="$SBTN_CMD" 27 | declare sbtn_version="1.10.0" 28 | 29 | ### ------------------------------- ### 30 | ### Helper methods for BASH scripts ### 31 | ### ------------------------------- ### 32 | 33 | # Bash reimplementation of realpath to return the absolute path 34 | realpathish () { 35 | ( 36 | TARGET_FILE="$1" 37 | FIX_CYGPATH="$2" 38 | 39 | cd "$(dirname "$TARGET_FILE")" 40 | TARGET_FILE=$(basename "$TARGET_FILE") 41 | 42 | COUNT=0 43 | while [ -L "$TARGET_FILE" -a $COUNT -lt 100 ] 44 | do 45 | TARGET_FILE=$(readlink "$TARGET_FILE") 46 | cd "$(dirname "$TARGET_FILE")" 47 | TARGET_FILE=$(basename "$TARGET_FILE") 48 | COUNT=$(($COUNT + 1)) 49 | done 50 | 51 | TARGET_DIR="$(pwd -P)" 52 | if [ "$TARGET_DIR" == "/" ]; then 53 | TARGET_FILE="/$TARGET_FILE" 54 | else 55 | TARGET_FILE="$TARGET_DIR/$TARGET_FILE" 56 | fi 57 | 58 | # make sure we grab the actual windows path, instead of cygwin's path. 59 | if [[ "x$FIX_CYGPATH" != "x" ]]; then 60 | echo "$(cygwinpath "$TARGET_FILE")" 61 | else 62 | echo "$TARGET_FILE" 63 | fi 64 | ) 65 | } 66 | 67 | # Uses uname to detect if we're in the odd cygwin environment. 68 | is_cygwin() { 69 | local os=$(uname -s) 70 | case "$os" in 71 | CYGWIN*) return 0 ;; 72 | MINGW*) return 0 ;; 73 | MSYS*) return 0 ;; 74 | *) return 1 ;; 75 | esac 76 | } 77 | 78 | # TODO - Use nicer bash-isms here. 79 | CYGWIN_FLAG=$(if is_cygwin; then echo true; else echo false; fi) 80 | 81 | # This can fix cygwin style /cygdrive paths so we get the 82 | # windows style paths. 83 | cygwinpath() { 84 | local file="$1" 85 | if [[ "$CYGWIN_FLAG" == "true" ]]; then #" 86 | echo $(cygpath -w $file) 87 | else 88 | echo $file 89 | fi 90 | } 91 | 92 | 93 | declare -r sbt_bin_dir="$(dirname "$(realpathish "$0")")" 94 | declare -r sbt_home="$(dirname "$sbt_bin_dir")" 95 | 96 | echoerr () { 97 | echo 1>&2 "$@" 98 | } 99 | vlog () { 100 | [[ $sbt_verbose || $sbt_debug ]] && echoerr "$@" 101 | } 102 | dlog () { 103 | [[ $sbt_debug ]] && echoerr "$@" 104 | } 105 | 106 | jar_file () { 107 | echo "$(cygwinpath "${sbt_home}/bin/sbt-launch.jar")" 108 | } 109 | 110 | jar_url () { 111 | local repo_base="$SBT_LAUNCH_REPO" 112 | if [[ $repo_base == "" ]]; then 113 | repo_base="https://repo1.maven.org/maven2" 114 | fi 115 | echo "$repo_base/org/scala-sbt/sbt-launch/$1/sbt-launch-$1.jar" 116 | } 117 | 118 | download_url () { 119 | local url="$1" 120 | local jar="$2" 121 | mkdir -p $(dirname "$jar") && { 122 | if command -v curl > /dev/null; then 123 | curl --silent -L "$url" --output "$jar" 124 | elif command -v wget > /dev/null; then 125 | wget --quiet -O "$jar" "$url" 126 | fi 127 | } && [[ -f "$jar" ]] 128 | } 129 | 130 | acquire_sbt_jar () { 131 | local launcher_sv="$1" 132 | if [[ "$launcher_sv" == "" ]]; then 133 | if [[ "$init_sbt_version" != "_to_be_replaced" ]]; then 134 | launcher_sv="$init_sbt_version" 135 | else 136 | launcher_sv="$builtin_sbt_version" 137 | fi 138 | fi 139 | local user_home && user_home=$(findProperty user.home) 140 | download_jar="${user_home:-$HOME}/.cache/sbt/boot/sbt-launch/$launcher_sv/sbt-launch-$launcher_sv.jar" 141 | if [[ -f "$download_jar" ]]; then 142 | sbt_jar="$download_jar" 143 | else 144 | sbt_url=$(jar_url "$launcher_sv") 145 | echoerr "downloading sbt launcher $launcher_sv" 146 | download_url "$sbt_url" "${download_jar}.temp" 147 | download_url "${sbt_url}.sha1" "${download_jar}.sha1" 148 | if command -v shasum > /dev/null; then 149 | if echo "$(cat "${download_jar}.sha1") ${download_jar}.temp" | shasum -c - > /dev/null; then 150 | mv "${download_jar}.temp" "${download_jar}" 151 | else 152 | echoerr "failed to download launcher jar: $sbt_url (shasum mismatch)" 153 | exit 2 154 | fi 155 | else 156 | mv "${download_jar}.temp" "${download_jar}" 157 | fi 158 | if [[ -f "$download_jar" ]]; then 159 | sbt_jar="$download_jar" 160 | else 161 | echoerr "failed to download launcher jar: $sbt_url" 162 | exit 2 163 | fi 164 | fi 165 | } 166 | 167 | acquire_sbtn () { 168 | local sbtn_v="$1" 169 | local user_home && user_home=$(findProperty user.home) 170 | local p="${user_home:-$HOME}/.cache/sbt/boot/sbtn/$sbtn_v" 171 | local target="$p/sbtn" 172 | local archive_target= 173 | local url= 174 | local arch="x86_64" 175 | if [[ "$OSTYPE" == "linux-gnu"* ]]; then 176 | arch=$(uname -m) 177 | if [[ "$arch" == "aarch64" ]] || [[ "$arch" == "x86_64" ]]; then 178 | archive_target="$p/sbtn-${arch}-pc-linux-${sbtn_v}.tar.gz" 179 | url="https://github.com/sbt/sbtn-dist/releases/download/v${sbtn_v}/sbtn-${arch}-pc-linux-${sbtn_v}.tar.gz" 180 | else 181 | echoerr "sbtn is not supported on $arch" 182 | exit 2 183 | fi 184 | elif [[ "$OSTYPE" == "darwin"* ]]; then 185 | archive_target="$p/sbtn-universal-apple-darwin-${sbtn_v}.tar.gz" 186 | url="https://github.com/sbt/sbtn-dist/releases/download/v${sbtn_v}/sbtn-universal-apple-darwin-${sbtn_v}.tar.gz" 187 | elif [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then 188 | target="$p/sbtn.exe" 189 | archive_target="$p/sbtn-x86_64-pc-win32-${sbtn_v}.zip" 190 | url="https://github.com/sbt/sbtn-dist/releases/download/v${sbtn_v}/sbtn-x86_64-pc-win32-${sbtn_v}.zip" 191 | else 192 | echoerr "sbtn is not supported on $OSTYPE" 193 | exit 2 194 | fi 195 | 196 | if [[ -f "$target" ]]; then 197 | sbtn_command="$target" 198 | else 199 | echoerr "downloading sbtn ${sbtn_v} for ${arch}" 200 | download_url "$url" "$archive_target" 201 | if [[ "$OSTYPE" == "linux-gnu"* ]] || [[ "$OSTYPE" == "darwin"* ]]; then 202 | tar zxf "$archive_target" --directory "$p" 203 | else 204 | unzip "$archive_target" -d "$p" 205 | fi 206 | sbtn_command="$target" 207 | fi 208 | } 209 | 210 | # execRunner should be called only once to give up control to java 211 | execRunner () { 212 | # print the arguments one to a line, quoting any containing spaces 213 | [[ $sbt_verbose || $sbt_debug ]] && echo "# Executing command line:" && { 214 | for arg; do 215 | if printf "%s\n" "$arg" | grep -q ' '; then 216 | printf "\"%s\"\n" "$arg" 217 | else 218 | printf "%s\n" "$arg" 219 | fi 220 | done 221 | echo "" 222 | } 223 | 224 | if [[ "$CYGWIN_FLAG" == "true" ]]; then 225 | # In cygwin we loose the ability to re-hook stty if exec is used 226 | # https://github.com/sbt/sbt-launcher-package/issues/53 227 | "$@" 228 | else 229 | exec "$@" 230 | fi 231 | } 232 | 233 | addJava () { 234 | dlog "[addJava] arg = '$1'" 235 | java_args=( "${java_args[@]}" "$1" ) 236 | } 237 | addSbt () { 238 | dlog "[addSbt] arg = '$1'" 239 | sbt_commands=( "${sbt_commands[@]}" "$1" ) 240 | } 241 | addResidual () { 242 | dlog "[residual] arg = '$1'" 243 | residual_args=( "${residual_args[@]}" "$1" ) 244 | } 245 | addDebugger () { 246 | addJava "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$1" 247 | } 248 | 249 | addMemory () { 250 | dlog "[addMemory] arg = '$1'" 251 | # evict memory related options 252 | local xs=("${java_args[@]}") 253 | java_args=() 254 | for i in "${xs[@]}"; do 255 | if ! [[ "${i}" == *-Xmx* ]] && ! [[ "${i}" == *-Xms* ]] && ! [[ "${i}" == *-Xss* ]] && ! [[ "${i}" == *-XX:MaxPermSize* ]] && ! [[ "${i}" == *-XX:MaxMetaspaceSize* ]] && ! [[ "${i}" == *-XX:ReservedCodeCacheSize* ]]; then 256 | java_args+=("${i}") 257 | fi 258 | done 259 | local ys=("${sbt_options[@]}") 260 | sbt_options=() 261 | for i in "${ys[@]}"; do 262 | if ! [[ "${i}" == *-Xmx* ]] && ! [[ "${i}" == *-Xms* ]] && ! [[ "${i}" == *-Xss* ]] && ! [[ "${i}" == *-XX:MaxPermSize* ]] && ! [[ "${i}" == *-XX:MaxMetaspaceSize* ]] && ! [[ "${i}" == *-XX:ReservedCodeCacheSize* ]]; then 263 | sbt_options+=("${i}") 264 | fi 265 | done 266 | # a ham-fisted attempt to move some memory settings in concert 267 | local mem=$1 268 | local codecache=$(( $mem / 8 )) 269 | (( $codecache > 128 )) || codecache=128 270 | (( $codecache < 512 )) || codecache=512 271 | local class_metadata_size=$(( $codecache * 2 )) 272 | if [[ -z $java_version ]]; then 273 | java_version=$(jdk_version) 274 | fi 275 | 276 | addJava "-Xms${mem}m" 277 | addJava "-Xmx${mem}m" 278 | addJava "-Xss4M" 279 | addJava "-XX:ReservedCodeCacheSize=${codecache}m" 280 | (( $java_version >= 8 )) || addJava "-XX:MaxPermSize=${class_metadata_size}m" 281 | } 282 | 283 | addDefaultMemory() { 284 | # if we detect any of these settings in ${JAVA_OPTS} or ${JAVA_TOOL_OPTIONS} we need to NOT output our settings. 285 | # The reason is the Xms/Xmx, if they don't line up, cause errors. 286 | if [[ "${java_args[@]}" == *-Xmx* ]] || \ 287 | [[ "${java_args[@]}" == *-Xms* ]] || \ 288 | [[ "${java_args[@]}" == *-Xss* ]] || \ 289 | [[ "${java_args[@]}" == *-XX:+UseCGroupMemoryLimitForHeap* ]] || \ 290 | [[ "${java_args[@]}" == *-XX:MaxRAM* ]] || \ 291 | [[ "${java_args[@]}" == *-XX:InitialRAMPercentage* ]] || \ 292 | [[ "${java_args[@]}" == *-XX:MaxRAMPercentage* ]] || \ 293 | [[ "${java_args[@]}" == *-XX:MinRAMPercentage* ]]; then 294 | : 295 | elif [[ "${JAVA_TOOL_OPTIONS}" == *-Xmx* ]] || \ 296 | [[ "${JAVA_TOOL_OPTIONS}" == *-Xms* ]] || \ 297 | [[ "${JAVA_TOOL_OPTIONS}" == *-Xss* ]] || \ 298 | [[ "${JAVA_TOOL_OPTIONS}" == *-XX:+UseCGroupMemoryLimitForHeap* ]] || \ 299 | [[ "${JAVA_TOOL_OPTIONS}" == *-XX:MaxRAM* ]] || \ 300 | [[ "${JAVA_TOOL_OPTIONS}" == *-XX:InitialRAMPercentage* ]] || \ 301 | [[ "${JAVA_TOOL_OPTIONS}" == *-XX:MaxRAMPercentage* ]] || \ 302 | [[ "${JAVA_TOOL_OPTIONS}" == *-XX:MinRAMPercentage* ]] ; then 303 | : 304 | elif [[ "${sbt_options[@]}" == *-Xmx* ]] || \ 305 | [[ "${sbt_options[@]}" == *-Xms* ]] || \ 306 | [[ "${sbt_options[@]}" == *-Xss* ]] || \ 307 | [[ "${sbt_options[@]}" == *-XX:+UseCGroupMemoryLimitForHeap* ]] || \ 308 | [[ "${sbt_options[@]}" == *-XX:MaxRAM* ]] || \ 309 | [[ "${sbt_options[@]}" == *-XX:InitialRAMPercentage* ]] || \ 310 | [[ "${sbt_options[@]}" == *-XX:MaxRAMPercentage* ]] || \ 311 | [[ "${sbt_options[@]}" == *-XX:MinRAMPercentage* ]] ; then 312 | : 313 | else 314 | addMemory $sbt_default_mem 315 | fi 316 | } 317 | 318 | addSbtScriptProperty () { 319 | if [[ "${java_args[@]}" == *-Dsbt.script=* ]]; then 320 | : 321 | else 322 | sbt_script=$0 323 | # Use // to replace all spaces with %20. 324 | sbt_script=${sbt_script// /%20} 325 | addJava "-Dsbt.script=$sbt_script" 326 | fi 327 | } 328 | 329 | require_arg () { 330 | local type="$1" 331 | local opt="$2" 332 | local arg="$3" 333 | if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then 334 | echo "$opt requires <$type> argument" 335 | exit 1 336 | fi 337 | } 338 | 339 | is_function_defined() { 340 | declare -f "$1" > /dev/null 341 | } 342 | 343 | # parses JDK version from the -version output line. 344 | # 8 for 1.8.0_nn, 9 for 9-ea etc, and "no_java" for undetected 345 | jdk_version() { 346 | local result 347 | local lines=$("$java_cmd" -Xms32M -Xmx32M -version 2>&1 | tr '\r' '\n') 348 | local IFS=$'\n' 349 | for line in $lines; do 350 | if [[ (-z $result) && ($line = *"version \""*) ]] 351 | then 352 | local ver=$(echo $line | sed -e 's/.*version "\(.*\)"\(.*\)/\1/; 1q') 353 | # on macOS sed doesn't support '?' 354 | if [[ $ver = "1."* ]] 355 | then 356 | result=$(echo $ver | sed -e 's/1\.\([0-9]*\)\(.*\)/\1/; 1q') 357 | else 358 | result=$(echo $ver | sed -e 's/\([0-9]*\)\(.*\)/\1/; 1q') 359 | fi 360 | fi 361 | done 362 | if [[ -z $result ]] 363 | then 364 | result=no_java 365 | fi 366 | echo "$result" 367 | } 368 | 369 | # Find the first occurrence of the given property name and returns its value by looking at: 370 | # - properties set by command-line options, 371 | # - JAVA_OPTS environment variable, 372 | # - SBT_OPTS environment variable, 373 | # - _JAVA_OPTIONS environment variable and 374 | # - JAVA_TOOL_OPTIONS environment variable 375 | # in that order. 376 | findProperty() { 377 | local -a java_opts_array 378 | local -a sbt_opts_array 379 | local -a _java_options_array 380 | local -a java_tool_options_array 381 | read -a java_opts_array <<< "$JAVA_OPTS" 382 | read -a sbt_opts_array <<< "$SBT_OPTS" 383 | read -a _java_options_array <<< "$_JAVA_OPTIONS" 384 | read -a java_tool_options_array <<< "$JAVA_TOOL_OPTIONS" 385 | 386 | local args_to_check=( 387 | "${java_args[@]}" 388 | "${java_opts_array[@]}" 389 | "${sbt_opts_array[@]}" 390 | "${_java_options_array[@]}" 391 | "${java_tool_options_array[@]}") 392 | 393 | for opt in "${args_to_check[@]}"; do 394 | if [[ "$opt" == -D$1=* ]]; then 395 | echo "${opt#-D$1=}" 396 | return 397 | fi 398 | done 399 | } 400 | 401 | # Extracts the preloaded directory from either -Dsbt.preloaded, -Dsbt.global.base or -Duser.home 402 | # in that order. 403 | getPreloaded() { 404 | local preloaded && preloaded=$(findProperty sbt.preloaded) 405 | [ "$preloaded" ] && echo "$preloaded" && return 406 | 407 | local global_base && global_base=$(findProperty sbt.global.base) 408 | [ "$global_base" ] && echo "$global_base/preloaded" && return 409 | 410 | local user_home && user_home=$(findProperty user.home) 411 | echo "${user_home:-$HOME}/.sbt/preloaded" 412 | } 413 | 414 | syncPreloaded() { 415 | local source_preloaded="$sbt_home/lib/local-preloaded/" 416 | local target_preloaded="$(getPreloaded)" 417 | if [[ "$init_sbt_version" == "" ]]; then 418 | # FIXME: better $init_sbt_version detection 419 | init_sbt_version="$(ls -1 "$source_preloaded/org/scala-sbt/sbt/")" 420 | fi 421 | [[ -f "$target_preloaded/org/scala-sbt/sbt/$init_sbt_version/" ]] || { 422 | # lib/local-preloaded exists (This is optional) 423 | [[ -d "$source_preloaded" ]] && { 424 | command -v rsync >/dev/null 2>&1 && { 425 | mkdir -p "$target_preloaded" 426 | rsync --recursive --links --perms --times --ignore-existing "$source_preloaded" "$target_preloaded" || true 427 | } 428 | } 429 | } 430 | } 431 | 432 | # Detect that we have java installed. 433 | checkJava() { 434 | local required_version="$1" 435 | # Now check to see if it's a good enough version 436 | local good_enough="$(expr $java_version ">=" $required_version)" 437 | if [[ "$java_version" == "" ]]; then 438 | echo 439 | echo "No Java Development Kit (JDK) installation was detected." 440 | echo Please go to http://www.oracle.com/technetwork/java/javase/downloads/ and download. 441 | echo 442 | exit 1 443 | elif [[ "$good_enough" != "1" ]]; then 444 | echo 445 | echo "The Java Development Kit (JDK) installation you have is not up to date." 446 | echo $script_name requires at least version $required_version+, you have 447 | echo version $java_version 448 | echo 449 | echo Please go to http://www.oracle.com/technetwork/java/javase/downloads/ and download 450 | echo a valid JDK and install before running $script_name. 451 | echo 452 | exit 1 453 | fi 454 | } 455 | 456 | copyRt() { 457 | local at_least_9="$(expr $java_version ">=" 9)" 458 | if [[ "$at_least_9" == "1" ]]; then 459 | # The grep for java9-rt-ext- matches the filename prefix printed in Export.java 460 | java9_ext=$("$java_cmd" "${sbt_options[@]}" "${java_args[@]}" \ 461 | -jar "$sbt_jar" --rt-ext-dir | grep java9-rt-ext- | tr -d '\r') 462 | java9_rt=$(echo "$java9_ext/rt.jar") 463 | vlog "[copyRt] java9_rt = '$java9_rt'" 464 | if [[ ! -f "$java9_rt" ]]; then 465 | echo copying runtime jar... 466 | mkdir -p "$java9_ext" 467 | "$java_cmd" \ 468 | "${sbt_options[@]}" \ 469 | "${java_args[@]}" \ 470 | -jar "$sbt_jar" \ 471 | --export-rt \ 472 | "${java9_rt}" 473 | fi 474 | addJava "-Dscala.ext.dirs=${java9_ext}" 475 | fi 476 | } 477 | 478 | run() { 479 | # Copy preloaded repo to user's preloaded directory 480 | syncPreloaded 481 | 482 | # no jar? download it. 483 | [[ -f "$sbt_jar" ]] || acquire_sbt_jar "$sbt_version" || { 484 | exit 1 485 | } 486 | 487 | # TODO - java check should be configurable... 488 | checkJava "6" 489 | 490 | # Java 9 support 491 | copyRt 492 | 493 | # If we're in cygwin, we should use the windows config, and terminal hacks 494 | if [[ "$CYGWIN_FLAG" == "true" ]]; then #" 495 | stty -icanon min 1 -echo > /dev/null 2>&1 496 | addJava "-Djline.terminal=jline.UnixTerminal" 497 | addJava "-Dsbt.cygwin=true" 498 | fi 499 | 500 | if [[ $print_sbt_version ]]; then 501 | execRunner "$java_cmd" -jar "$sbt_jar" "sbtVersion" | tail -1 | sed -e 's/\[info\]//g' 502 | elif [[ $print_sbt_script_version ]]; then 503 | echo "$init_sbt_version" 504 | elif [[ $print_version ]]; then 505 | execRunner "$java_cmd" -jar "$sbt_jar" "sbtVersion" | tail -1 | sed -e 's/\[info\]/sbt version in this project:/g' 506 | echo "sbt script version: $init_sbt_version" 507 | elif [[ $shutdownall ]]; then 508 | local sbt_processes=( $(jps -v | grep sbt-launch | cut -f1 -d ' ') ) 509 | for procId in "${sbt_processes[@]}"; do 510 | kill -9 $procId 511 | done 512 | echo "shutdown ${#sbt_processes[@]} sbt processes" 513 | else 514 | # run sbt 515 | execRunner "$java_cmd" \ 516 | "${java_args[@]}" \ 517 | "${sbt_options[@]}" \ 518 | "${java_tool_options[@]}" \ 519 | -jar "$sbt_jar" \ 520 | "${sbt_commands[@]}" \ 521 | "${residual_args[@]}" 522 | fi 523 | 524 | exit_code=$? 525 | 526 | # Clean up the terminal from cygwin hacks. 527 | if [[ "$CYGWIN_FLAG" == "true" ]]; then #" 528 | stty icanon echo > /dev/null 2>&1 529 | fi 530 | exit $exit_code 531 | } 532 | 533 | declare -ra noshare_opts=(-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy) 534 | declare -r sbt_opts_file=".sbtopts" 535 | declare -r build_props_file="$(pwd)/project/build.properties" 536 | declare -r etc_sbt_opts_file="/etc/sbt/sbtopts" 537 | # this allows /etc/sbt/sbtopts location to be changed 538 | declare -r etc_file="${SBT_ETC_FILE:-$etc_sbt_opts_file}" 539 | declare -r dist_sbt_opts_file="${sbt_home}/conf/sbtopts" 540 | declare -r win_sbt_opts_file="${sbt_home}/conf/sbtconfig.txt" 541 | declare sbt_jar="$(jar_file)" 542 | 543 | usage() { 544 | cat < path to global settings/plugins directory (default: ~/.sbt) 565 | --sbt-boot path to shared boot directory (default: ~/.sbt/boot in 0.11 series) 566 | --sbt-cache path to global cache directory (default: operating system specific) 567 | --ivy path to local Ivy repository (default: ~/.ivy2) 568 | --mem set memory options (default: $sbt_default_mem) 569 | --no-share use all local caches; no sharing 570 | --no-global uses global caches, but does not use global ~/.sbt directory. 571 | --jvm-debug Turn on JVM debugging, open at the given port. 572 | --batch disable interactive mode 573 | 574 | # sbt version (default: from project/build.properties if present, else latest release) 575 | --sbt-version use the specified version of sbt 576 | --sbt-jar use the specified jar as the sbt launcher 577 | 578 | --java-home alternate JAVA_HOME 579 | 580 | # jvm options and output control 581 | JAVA_OPTS environment variable, if unset uses "$default_java_opts" 582 | .jvmopts if this file exists in the current directory, its contents 583 | are appended to JAVA_OPTS 584 | SBT_OPTS environment variable, if unset uses "$default_sbt_opts" 585 | .sbtopts if this file exists in the current directory, its contents 586 | are prepended to the runner args 587 | /etc/sbt/sbtopts if this file exists, it is prepended to the runner args 588 | -Dkey=val pass -Dkey=val directly to the java runtime 589 | -J-X pass option -X directly to the java runtime 590 | (-J is stripped) 591 | 592 | In the case of duplicated or conflicting options, the order above 593 | shows precedence: JAVA_OPTS lowest, command line options highest. 594 | EOM 595 | } 596 | 597 | process_my_args () { 598 | while [[ $# -gt 0 ]]; do 599 | case "$1" in 600 | -batch|--batch) exec 601 | 602 | -sbt-create|--sbt-create) sbt_create=true && shift ;; 603 | 604 | new) sbt_new=true && addResidual "$1" && shift ;; 605 | 606 | *) addResidual "$1" && shift ;; 607 | esac 608 | done 609 | 610 | # Now, ensure sbt version is used. 611 | [[ "${sbt_version}XXX" != "XXX" ]] && addJava "-Dsbt.version=$sbt_version" 612 | 613 | # Confirm a user's intent if the current directory does not look like an sbt 614 | # top-level directory and neither the -sbt-create option nor the "new" 615 | # command was given. 616 | [[ -f ./build.sbt || -d ./project || -n "$sbt_create" || -n "$sbt_new" ]] || { 617 | echo "[warn] Neither build.sbt nor a 'project' directory in the current directory: $(pwd)" 618 | while true; do 619 | echo 'c) continue' 620 | echo 'q) quit' 621 | 622 | read -p '? ' || exit 1 623 | case "$REPLY" in 624 | c|C) break ;; 625 | q|Q) exit 1 ;; 626 | esac 627 | done 628 | } 629 | } 630 | 631 | ## map over argument array. this is used to process both command line arguments and SBT_OPTS 632 | map_args () { 633 | local options=() 634 | local commands=() 635 | while [[ $# -gt 0 ]]; do 636 | case "$1" in 637 | -no-colors|--no-colors) options=( "${options[@]}" "-Dsbt.log.noformat=true" ) && shift ;; 638 | -timings|--timings) options=( "${options[@]}" "-Dsbt.task.timings=true" "-Dsbt.task.timings.on.shutdown=true" ) && shift ;; 639 | -traces|--traces) options=( "${options[@]}" "-Dsbt.traces=true" ) && shift ;; 640 | --supershell=*) options=( "${options[@]}" "-Dsbt.supershell=${1:13}" ) && shift ;; 641 | -supershell=*) options=( "${options[@]}" "-Dsbt.supershell=${1:12}" ) && shift ;; 642 | -no-server|--no-server) options=( "${options[@]}" "-Dsbt.io.virtual=false" "-Dsbt.server.autostart=false" ) && shift ;; 643 | --color=*) options=( "${options[@]}" "-Dsbt.color=${1:8}" ) && shift ;; 644 | -color=*) options=( "${options[@]}" "-Dsbt.color=${1:7}" ) && shift ;; 645 | -no-share|--no-share) options=( "${options[@]}" "${noshare_opts[@]}" ) && shift ;; 646 | -no-global|--no-global) options=( "${options[@]}" "-Dsbt.global.base=$(pwd)/project/.sbtboot" ) && shift ;; 647 | -ivy|--ivy) require_arg path "$1" "$2" && options=( "${options[@]}" "-Dsbt.ivy.home=$2" ) && shift 2 ;; 648 | -sbt-boot|--sbt-boot) require_arg path "$1" "$2" && options=( "${options[@]}" "-Dsbt.boot.directory=$2" ) && shift 2 ;; 649 | -sbt-dir|--sbt-dir) require_arg path "$1" "$2" && options=( "${options[@]}" "-Dsbt.global.base=$2" ) && shift 2 ;; 650 | -debug|--debug) commands=( "${commands[@]}" "-debug" ) && shift ;; 651 | -debug-inc|--debug-inc) options=( "${options[@]}" "-Dxsbt.inc.debug=true" ) && shift ;; 652 | *) options=( "${options[@]}" "$1" ) && shift ;; 653 | esac 654 | done 655 | declare -p options 656 | declare -p commands 657 | } 658 | 659 | process_args () { 660 | while [[ $# -gt 0 ]]; do 661 | case "$1" in 662 | -h|-help|--help) usage; exit 1 ;; 663 | -v|-verbose|--verbose) sbt_verbose=1 && shift ;; 664 | -V|-version|--version) print_version=1 && shift ;; 665 | --numeric-version) print_sbt_version=1 && shift ;; 666 | --script-version) print_sbt_script_version=1 && shift ;; 667 | shutdownall) shutdownall=1 && shift ;; 668 | -d|-debug|--debug) sbt_debug=1 && addSbt "-debug" && shift ;; 669 | -client|--client) use_sbtn=1 && shift ;; 670 | --server) use_sbtn=0 && shift ;; 671 | 672 | -mem|--mem) require_arg integer "$1" "$2" && addMemory "$2" && shift 2 ;; 673 | -jvm-debug|--jvm-debug) require_arg port "$1" "$2" && addDebugger $2 && shift 2 ;; 674 | -batch|--batch) exec = 2 )) || ( (( $sbtBinaryV_1 >= 1 )) && (( $sbtBinaryV_2 >= 4 )) ); then 742 | if [[ "$use_sbtn" == "1" ]]; then 743 | echo "true" 744 | else 745 | echo "false" 746 | fi 747 | else 748 | echo "false" 749 | fi 750 | } 751 | 752 | runNativeClient() { 753 | vlog "[debug] running native client" 754 | detectNativeClient 755 | [[ -f "$sbtn_command" ]] || acquire_sbtn "$sbtn_version" || { 756 | exit 1 757 | } 758 | for i in "${!original_args[@]}"; do 759 | if [[ "${original_args[i]}" = "--client" ]]; then 760 | unset 'original_args[i]' 761 | fi 762 | done 763 | sbt_script=$0 764 | sbt_script=${sbt_script/ /%20} 765 | execRunner "$sbtn_command" "--sbt-script=$sbt_script" "${original_args[@]}" 766 | } 767 | 768 | original_args=("$@") 769 | 770 | # Here we pull in the default settings configuration. 771 | [[ -f "$dist_sbt_opts_file" ]] && set -- $(loadConfigFile "$dist_sbt_opts_file") "$@" 772 | 773 | # Here we pull in the global settings configuration. 774 | [[ -f "$etc_file" ]] && set -- $(loadConfigFile "$etc_file") "$@" 775 | 776 | # Pull in the project-level config file, if it exists. 777 | [[ -f "$sbt_opts_file" ]] && set -- $(loadConfigFile "$sbt_opts_file") "$@" 778 | 779 | # Pull in the project-level java config, if it exists. 780 | [[ -f ".jvmopts" ]] && export JAVA_OPTS="$JAVA_OPTS $(loadConfigFile .jvmopts)" 781 | 782 | # Pull in default JAVA_OPTS 783 | [[ -z "${JAVA_OPTS// }" ]] && export JAVA_OPTS="$default_java_opts" 784 | 785 | [[ -f "$build_props_file" ]] && loadPropFile "$build_props_file" 786 | 787 | java_args=($JAVA_OPTS) 788 | sbt_options0=(${SBT_OPTS:-$default_sbt_opts}) 789 | java_tool_options=($JAVA_TOOL_OPTIONS) 790 | if [[ "$SBT_NATIVE_CLIENT" == "true" ]]; then 791 | use_sbtn=1 792 | fi 793 | 794 | # Split SBT_OPTS into options/commands 795 | miniscript=$(map_args "${sbt_options0[@]}") && eval "${miniscript/options/sbt_options}" && \ 796 | eval "${miniscript/commands/sbt_additional_commands}" 797 | 798 | # Combine command line options/commands and commands from SBT_OPTS 799 | miniscript=$(map_args "$@") && eval "${miniscript/options/cli_options}" && eval "${miniscript/commands/cli_commands}" 800 | args1=( "${cli_options[@]}" "${cli_commands[@]}" "${sbt_additional_commands[@]}" ) 801 | 802 | # process the combined args, then reset "$@" to the residuals 803 | process_args "${args1[@]}" 804 | vlog "[sbt_options] $(declare -p sbt_options)" 805 | 806 | if [[ "$(isRunNativeClient)" == "true" ]]; then 807 | set -- "${residual_args[@]}" 808 | argumentCount=$# 809 | runNativeClient 810 | else 811 | java_version="$(jdk_version)" 812 | vlog "[process_args] java_version = '$java_version'" 813 | addDefaultMemory 814 | addSbtScriptProperty 815 | set -- "${residual_args[@]}" 816 | argumentCount=$# 817 | run 818 | fi 819 | -------------------------------------------------------------------------------- /sbt.bat: -------------------------------------------------------------------------------- 1 | @REM SBT launcher script 2 | @REM 3 | @REM Environment: 4 | @REM JAVA_HOME - location of a JDK home dir (mandatory) 5 | @REM SBT_OPTS - JVM options (optional) 6 | @REM Configuration: 7 | @REM sbtconfig.txt found in the SBT_HOME. 8 | 9 | @REM ZOMG! We need delayed expansion to build up CFG_OPTS later 10 | @setlocal enabledelayedexpansion 11 | 12 | @echo off 13 | set SBT_BIN_DIR=%~dp0 14 | if not defined SBT_HOME for %%I in ("!SBT_BIN_DIR!\..") do set "SBT_HOME=%%~fI" 15 | 16 | set SBT_ARGS= 17 | set _JAVACMD= 18 | set _SBT_OPTS= 19 | set _JAVA_OPTS= 20 | 21 | set init_sbt_version=1.10.1 22 | set sbt_default_mem=1024 23 | set default_sbt_opts= 24 | set default_java_opts=-Dfile.encoding=UTF-8 25 | set sbt_jar= 26 | set build_props_sbt_version= 27 | set run_native_client= 28 | set shutdownall= 29 | 30 | set sbt_args_print_version= 31 | set sbt_args_print_sbt_version= 32 | set sbt_args_print_sbt_script_version= 33 | set sbt_args_verbose= 34 | set sbt_args_debug= 35 | set sbt_args_debug_inc= 36 | set sbt_args_batch= 37 | set sbt_args_color= 38 | set sbt_args_no_colors= 39 | set sbt_args_no_global= 40 | set sbt_args_no_share= 41 | set sbt_args_sbt_jar= 42 | set sbt_args_ivy= 43 | set sbt_args_supershell= 44 | set sbt_args_timings= 45 | set sbt_args_traces= 46 | set sbt_args_sbt_boot= 47 | set sbt_args_sbt_cache= 48 | set sbt_args_sbt_create= 49 | set sbt_args_sbt_dir= 50 | set sbt_args_sbt_version= 51 | set sbt_args_mem= 52 | set sbt_args_client= 53 | set sbt_args_no_server= 54 | 55 | rem users can set SBT_OPTS via .sbtopts 56 | if exist .sbtopts for /F %%A in (.sbtopts) do ( 57 | set _sbtopts_line=%%A 58 | if not "!_sbtopts_line:~0,1!" == "#" ( 59 | if "!_sbtopts_line:~0,2!" == "-J" ( 60 | set _sbtopts_line=!_sbtopts_line:~2,1000! 61 | ) 62 | if defined _SBT_OPTS ( 63 | set _SBT_OPTS=!_SBT_OPTS! !_sbtopts_line! 64 | ) else ( 65 | set _SBT_OPTS=!_sbtopts_line! 66 | ) 67 | ) 68 | ) 69 | 70 | rem TODO: remove/deprecate sbtconfig.txt and parse the sbtopts files 71 | 72 | rem FIRST we load the config file of extra options. 73 | set SBT_CONFIG=!SBT_HOME!\conf\sbtconfig.txt 74 | set SBT_CFG_OPTS= 75 | for /F "tokens=* eol=# usebackq delims=" %%i in ("!SBT_CONFIG!") do ( 76 | set DO_NOT_REUSE_ME=%%i 77 | rem ZOMG (Part #2) WE use !! here to delay the expansion of 78 | rem SBT_CFG_OPTS, otherwise it remains "" for this loop. 79 | set SBT_CFG_OPTS=!SBT_CFG_OPTS! !DO_NOT_REUSE_ME! 80 | ) 81 | 82 | rem poor man's jenv (which is not available on Windows) 83 | if defined JAVA_HOMES ( 84 | if exist .java-version for /F %%A in (.java-version) do ( 85 | set JAVA_HOME=%JAVA_HOMES%\%%A 86 | set JDK_HOME=%JAVA_HOMES%\%%A 87 | ) 88 | ) 89 | 90 | if exist "project\build.properties" ( 91 | for /F "eol=# delims== tokens=1*" %%a in (project\build.properties) do ( 92 | if "%%a" == "sbt.version" if not "%%b" == "" ( 93 | set build_props_sbt_version=%%b 94 | ) 95 | ) 96 | ) 97 | 98 | rem must set PATH or wrong javac is used for java projects 99 | if defined JAVA_HOME set "PATH=%JAVA_HOME%\bin;%PATH%" 100 | 101 | rem We use the value of the JAVACMD environment variable if defined 102 | if defined JAVACMD set "_JAVACMD=%JAVACMD%" 103 | 104 | rem remove quotes 105 | if defined _JAVACMD set _JAVACMD=!_JAVACMD:"=! 106 | 107 | if not defined _JAVACMD ( 108 | if not "%JAVA_HOME%" == "" ( 109 | if exist "%JAVA_HOME%\bin\java.exe" set "_JAVACMD=%JAVA_HOME%\bin\java.exe" 110 | ) 111 | ) 112 | 113 | if not defined _JAVACMD set _JAVACMD=java 114 | 115 | rem We use the value of the JAVA_OPTS environment variable if defined, rather than the config. 116 | if not defined _JAVA_OPTS if defined JAVA_OPTS set _JAVA_OPTS=%JAVA_OPTS% 117 | 118 | rem users can set JAVA_OPTS via .jvmopts (sbt-extras style) 119 | if exist .jvmopts for /F %%A in (.jvmopts) do ( 120 | set _jvmopts_line=%%A 121 | if not "!_jvmopts_line:~0,1!" == "#" ( 122 | if defined _JAVA_OPTS ( 123 | set _JAVA_OPTS=!_JAVA_OPTS! %%A 124 | ) else ( 125 | set _JAVA_OPTS=%%A 126 | ) 127 | ) 128 | ) 129 | 130 | rem If nothing is defined, use the defaults. 131 | if not defined _JAVA_OPTS if defined default_java_opts set _JAVA_OPTS=!default_java_opts! 132 | 133 | rem We use the value of the SBT_OPTS environment variable if defined, rather than the config. 134 | if not defined _SBT_OPTS if defined SBT_OPTS set _SBT_OPTS=%SBT_OPTS% 135 | if not defined _SBT_OPTS if defined SBT_CFG_OPTS set _SBT_OPTS=!SBT_CFG_OPTS! 136 | if not defined _SBT_OPTS if defined default_sbt_opts set _SBT_OPTS=!default_sbt_opts! 137 | 138 | if defined SBT_NATIVE_CLIENT ( 139 | if "%SBT_NATIVE_CLIENT%" == "true" ( 140 | set sbt_args_client=1 141 | ) 142 | ) 143 | 144 | :args_loop 145 | shift 146 | 147 | if "%~0" == "" goto args_end 148 | set g=%~0 149 | 150 | rem make sure the sbt_args_debug gets set first incase any argument parsing uses :dlog 151 | if "%~0" == "-d" set _debug_arg=true 152 | if "%~0" == "--debug" set _debug_arg=true 153 | 154 | if defined _debug_arg ( 155 | set _debug_arg= 156 | set sbt_args_debug=1 157 | set SBT_ARGS=-debug !SBT_ARGS! 158 | goto args_loop 159 | ) 160 | 161 | if "%~0" == "-h" goto usage 162 | if "%~0" == "-help" goto usage 163 | if "%~0" == "--help" goto usage 164 | 165 | if "%~0" == "-v" set _verbose_arg=true 166 | if "%~0" == "-verbose" set _verbose_arg=true 167 | if "%~0" == "--verbose" set _verbose_arg=true 168 | 169 | if defined _verbose_arg ( 170 | set _verbose_arg= 171 | set sbt_args_verbose=1 172 | goto args_loop 173 | ) 174 | 175 | if "%~0" == "-V" set _version_arg=true 176 | if "%~0" == "-version" set _version_arg=true 177 | if "%~0" == "--version" set _version_arg=true 178 | 179 | if defined _version_arg ( 180 | set _version_arg= 181 | set sbt_args_print_version=1 182 | goto args_loop 183 | ) 184 | 185 | if "%~0" == "--client" set _client_arg=true 186 | 187 | if defined _client_arg ( 188 | set _client_arg= 189 | set sbt_args_client=1 190 | goto args_loop 191 | ) 192 | 193 | if "%~0" == "-batch" set _batch_arg=true 194 | if "%~0" == "--batch" set _batch_arg=true 195 | 196 | if defined _batch_arg ( 197 | set _batch_arg= 198 | set sbt_args_batch=1 199 | goto args_loop 200 | ) 201 | 202 | if "%~0" == "-no-colors" set _no_colors_arg=true 203 | if "%~0" == "--no-colors" set _no_colors_arg=true 204 | 205 | if defined _no_colors_arg ( 206 | set _no_colors_arg= 207 | set sbt_args_no_colors=1 208 | goto args_loop 209 | ) 210 | 211 | if "%~0" == "-no-server" set _no_server_arg=true 212 | if "%~0" == "--no-server" set _no_server_arg=true 213 | 214 | if defined _no_server_arg ( 215 | set _no_server_arg= 216 | set sbt_args_no_server=1 217 | goto args_loop 218 | ) 219 | 220 | if "%~0" == "-no-global" set _no_global_arg=true 221 | if "%~0" == "--no-global" set _no_global_arg=true 222 | 223 | if defined _no_global_arg ( 224 | set _no_global_arg= 225 | set sbt_args_no_global=1 226 | goto args_loop 227 | ) 228 | 229 | if "%~0" == "-traces" set _traces_arg=true 230 | if "%~0" == "--traces" set _traces_arg=true 231 | 232 | if defined _traces_arg ( 233 | set _traces_arg= 234 | set sbt_args_traces=1 235 | goto args_loop 236 | ) 237 | 238 | if "%~0" == "-sbt-create" set _sbt_create_arg=true 239 | if "%~0" == "--sbt-create" set _sbt_create_arg=true 240 | 241 | if defined _sbt_create_arg ( 242 | set _sbt_create_arg= 243 | set sbt_args_sbt_create=1 244 | goto args_loop 245 | ) 246 | 247 | if "%~0" == "-sbt-dir" set _sbt_dir_arg=true 248 | if "%~0" == "--sbt-dir" set _sbt_dir_arg=true 249 | 250 | if defined _sbt_dir_arg ( 251 | set _sbt_dir_arg= 252 | if not "%~1" == "" ( 253 | set sbt_args_sbt_dir=%1 254 | shift 255 | goto args_loop 256 | ) else ( 257 | echo "%~0" is missing a value 258 | goto error 259 | ) 260 | ) 261 | 262 | if "%~0" == "-sbt-boot" set _sbt_boot_arg=true 263 | if "%~0" == "--sbt-boot" set _sbt_boot_arg=true 264 | 265 | if defined _sbt_boot_arg ( 266 | set _sbt_boot_arg= 267 | if not "%~1" == "" ( 268 | set sbt_args_sbt_boot=%1 269 | shift 270 | goto args_loop 271 | ) else ( 272 | echo "%~0" is missing a value 273 | goto error 274 | ) 275 | ) 276 | 277 | if "%~0" == "-sbt-cache" set _sbt_cache_arg=true 278 | if "%~0" == "--sbt-cache" set _sbt_cache_arg=true 279 | 280 | if defined _sbt_cache_arg ( 281 | set _sbt_cache_arg= 282 | if not "%~1" == "" ( 283 | set sbt_args_sbt_cache=%1 284 | shift 285 | goto args_loop 286 | ) else ( 287 | echo "%~0" is missing a value 288 | goto error 289 | ) 290 | ) 291 | 292 | if "%~0" == "-sbt-jar" set _sbt_jar=true 293 | if "%~0" == "--sbt-jar" set _sbt_jar=true 294 | 295 | if defined _sbt_jar ( 296 | set _sbt_jar= 297 | if not "%~1" == "" ( 298 | if exist "%~1" ( 299 | set sbt_args_sbt_jar=%1 300 | shift 301 | goto args_loop 302 | ) else ( 303 | echo %~1 does not exist 304 | goto error 305 | ) 306 | ) else ( 307 | echo "%~0" is missing a value 308 | goto error 309 | ) 310 | ) 311 | 312 | if "%~0" == "-ivy" set _sbt_ivy_arg=true 313 | if "%~0" == "--ivy" set _sbt_ivy_arg=true 314 | 315 | if defined _sbt_ivy_arg ( 316 | set _sbt_ivy_arg= 317 | if not "%~1" == "" ( 318 | set sbt_args_ivy=%1 319 | shift 320 | goto args_loop 321 | ) else ( 322 | echo "%~0" is missing a value 323 | goto error 324 | ) 325 | ) 326 | 327 | if "%~0" == "-debug-inc" set _debug_inc_arg=true 328 | if "%~0" == "--debug-inc" set _debug_inc_arg=true 329 | 330 | if defined _debug_inc_arg ( 331 | set _debug_inc_arg= 332 | set sbt_args_debug_inc=1 333 | goto args_loop 334 | ) 335 | 336 | if "%~0" == "--sbt-version" set _sbt_version_arg=true 337 | if "%~0" == "-sbt-version" set _sbt_version_arg=true 338 | 339 | if defined _sbt_version_arg ( 340 | set _sbt_version_arg= 341 | if not "%~1" == "" ( 342 | set sbt_args_sbt_version=%~1 343 | shift 344 | goto args_loop 345 | ) else ( 346 | echo "%~0" is missing a value 347 | goto error 348 | ) 349 | ) 350 | 351 | if "%~0" == "--mem" set _sbt_mem_arg=true 352 | if "%~0" == "-mem" set _sbt_mem_arg=true 353 | 354 | if defined _sbt_mem_arg ( 355 | set _sbt_mem_arg= 356 | if not "%~1" == "" ( 357 | set sbt_args_mem=%~1 358 | shift 359 | goto args_loop 360 | ) else ( 361 | echo "%~0" is missing a value 362 | goto error 363 | ) 364 | ) 365 | 366 | if "%~0" == "--supershell" set _supershell_arg=true 367 | if "%~0" == "-supershell" set _supershell_arg=true 368 | 369 | if defined _supershell_arg ( 370 | set _supershell_arg= 371 | if not "%~1" == "" ( 372 | set sbt_args_supershell=%~1 373 | shift 374 | goto args_loop 375 | ) else ( 376 | echo "%~0" is missing a value 377 | goto error 378 | ) 379 | ) 380 | 381 | if "%~0" == "--color" set _color_arg=true 382 | if "%~0" == "-color" set _color_arg=true 383 | 384 | if defined _color_arg ( 385 | set _color_arg= 386 | if not "%~1" == "" ( 387 | set sbt_args_color=%~1 388 | shift 389 | goto args_loop 390 | ) else ( 391 | echo "%~0" is missing a value 392 | goto error 393 | ) 394 | goto args_loop 395 | ) 396 | 397 | if "%~0" == "--no-share" set _no_share_arg=true 398 | if "%~0" == "-no-share" set _no_share_arg=true 399 | 400 | if defined _no_share_arg ( 401 | set _no_share_arg= 402 | set sbt_args_no_share=1 403 | goto args_loop 404 | ) 405 | 406 | if "%~0" == "--timings" set _timings_arg=true 407 | if "%~0" == "-timings" set _timings_arg=true 408 | 409 | if defined _timings_arg ( 410 | set _timings_arg= 411 | set sbt_args_timings=1 412 | goto args_loop 413 | ) 414 | 415 | if "%~0" == "shutdownall" ( 416 | set shutdownall=1 417 | goto args_loop 418 | ) 419 | 420 | if "%~0" == "--script-version" ( 421 | set sbt_args_print_sbt_script_version=1 422 | goto args_loop 423 | ) 424 | 425 | if "%~0" == "--numeric-version" ( 426 | set sbt_args_print_sbt_version=1 427 | goto args_loop 428 | ) 429 | 430 | if "%~0" == "-jvm-debug" set _jvm_debug_arg=true 431 | if "%~0" == "--jvm-debug" set _jvm_debug_arg=true 432 | 433 | if defined _jvm_debug_arg ( 434 | set _jvm_debug_arg= 435 | if not "%~1" == "" ( 436 | set /a JVM_DEBUG_PORT=%~1 2>nul >nul 437 | if !JVM_DEBUG_PORT! EQU 0 ( 438 | rem next argument wasn't a port, set a default and process next arg 439 | set /A JVM_DEBUG_PORT=5005 440 | goto args_loop 441 | ) else ( 442 | shift 443 | goto args_loop 444 | ) 445 | ) 446 | ) 447 | 448 | if "%~0" == "-java-home" set _java_home_arg=true 449 | if "%~0" == "--java-home" set _java_home_arg=true 450 | 451 | if defined _java_home_arg ( 452 | set _java_home_arg= 453 | if not "%~1" == "" ( 454 | if exist "%~1\bin\java.exe" ( 455 | set "_JAVACMD=%~1\bin\java.exe" 456 | set "JAVA_HOME=%~1" 457 | set "JDK_HOME=%~1" 458 | shift 459 | goto args_loop 460 | ) else ( 461 | echo Directory "%~1" for JAVA_HOME is not valid 462 | goto error 463 | ) 464 | ) else ( 465 | echo Second argument for --java-home missing 466 | goto error 467 | ) 468 | ) 469 | 470 | if "%~0" == "new" ( 471 | if not defined SBT_ARGS ( 472 | set sbt_new=true 473 | ) 474 | ) 475 | 476 | if "%g:~0,2%" == "-D" ( 477 | rem special handling for -D since '=' gets parsed away 478 | for /F "tokens=1 delims==" %%a in ("%g%") do ( 479 | rem make sure it doesn't have the '=' already 480 | if "%g%" == "%%a" ( 481 | if not "%~1" == "" ( 482 | call :dlog [args_loop] -D argument %~0=%~1 483 | set "SBT_ARGS=!SBT_ARGS! %~0=%~1" 484 | shift 485 | goto args_loop 486 | ) else ( 487 | echo %g% is missing a value 488 | goto error 489 | ) 490 | ) else ( 491 | call :dlog [args_loop] -D argument %~0 492 | set "SBT_ARGS=!SBT_ARGS! %~0" 493 | goto args_loop 494 | ) 495 | ) 496 | ) 497 | 498 | if not "%g:~0,5%" == "-XX:+" if not "%g:~0,5%" == "-XX:-" if "%g:~0,3%" == "-XX" ( 499 | rem special handling for -XX since '=' gets parsed away 500 | for /F "tokens=1 delims==" %%a in ("%g%") do ( 501 | rem make sure it doesn't have the '=' already 502 | if "%g%" == "%%a" ( 503 | if not "%~1" == "" ( 504 | call :dlog [args_loop] -XX argument %~0=%~1 505 | set "SBT_ARGS=!SBT_ARGS! %~0=%~1" 506 | shift 507 | goto args_loop 508 | ) else ( 509 | echo %g% is missing a value 510 | goto error 511 | ) 512 | ) else ( 513 | call :dlog [args_loop] -XX argument %~0 514 | set "SBT_ARGS=!SBT_ARGS! %~0" 515 | goto args_loop 516 | ) 517 | ) 518 | ) 519 | 520 | rem the %0 (instead of %~0) preserves original argument quoting 521 | set SBT_ARGS=!SBT_ARGS! %0 522 | 523 | goto args_loop 524 | :args_end 525 | 526 | rem Confirm a user's intent if the current directory does not look like an sbt 527 | rem top-level directory and the "new" command was not given. 528 | 529 | if not defined sbt_args_sbt_create if not defined sbt_args_print_version if not defined sbt_args_print_sbt_version if not defined sbt_args_print_sbt_script_version if not defined shutdownall if not exist build.sbt ( 530 | if not exist project\ ( 531 | if not defined sbt_new ( 532 | echo [warn] Neither build.sbt nor a 'project' directory in the current directory: "%CD%" 533 | setlocal 534 | :confirm 535 | echo c^) continue 536 | echo q^) quit 537 | 538 | set /P reply=^? 539 | if /I "!reply!" == "c" ( 540 | goto confirm_end 541 | ) else if /I "!reply!" == "q" ( 542 | exit /B 1 543 | ) 544 | 545 | goto confirm 546 | :confirm_end 547 | endlocal 548 | ) 549 | ) 550 | ) 551 | 552 | call :process 553 | 554 | rem avoid bootstrapping/java version check for script version 555 | 556 | if !shutdownall! equ 1 ( 557 | set count=0 558 | for /f "tokens=1" %%i in ('jps -lv ^| findstr "xsbt.boot.Boot"') do ( 559 | taskkill /F /PID %%i 560 | set /a count=!count!+1 561 | ) 562 | echo shutdown !count! sbt processes 563 | goto :eof 564 | ) 565 | 566 | if !sbt_args_print_sbt_script_version! equ 1 ( 567 | echo !init_sbt_version! 568 | goto :eof 569 | ) 570 | 571 | if !run_native_client! equ 1 ( 572 | goto :runnative !SBT_ARGS! 573 | goto :eof 574 | ) 575 | 576 | call :checkjava 577 | 578 | if defined sbt_args_sbt_jar ( 579 | set "sbt_jar=!sbt_args_sbt_jar!" 580 | ) else ( 581 | set "sbt_jar=!SBT_HOME!\bin\sbt-launch.jar" 582 | ) 583 | 584 | set sbt_jar=!sbt_jar:"=! 585 | 586 | call :copyrt 587 | 588 | if defined JVM_DEBUG_PORT ( 589 | set _JAVA_OPTS=!_JAVA_OPTS! -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=!JVM_DEBUG_PORT! 590 | ) 591 | 592 | call :sync_preloaded 593 | 594 | call :run !SBT_ARGS! 595 | 596 | if ERRORLEVEL 1 goto error 597 | goto end 598 | 599 | :run 600 | 601 | rem set arguments 602 | 603 | if defined sbt_args_debug_inc ( 604 | set _SBT_OPTS=-Dxsbt.inc.debug=true !_SBT_OPTS! 605 | ) 606 | 607 | if defined sbt_args_no_colors ( 608 | set _SBT_OPTS=-Dsbt.log.noformat=true !_SBT_OPTS! 609 | ) 610 | 611 | if defined sbt_args_no_global ( 612 | set _SBT_OPTS=-Dsbt.global.base=project/.sbtboot !_SBT_OPTS! 613 | ) 614 | 615 | if defined sbt_args_no_share ( 616 | set _SBT_OPTS=-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy !_SBT_OPTS! 617 | ) 618 | 619 | if defined sbt_args_supershell ( 620 | set _SBT_OPTS=-Dsbt.supershell=!sbt_args_supershell! !_SBT_OPTS! 621 | ) 622 | 623 | if defined sbt_args_sbt_version ( 624 | set _SBT_OPTS=-Dsbt.version=!sbt_args_sbt_version! !_SBT_OPTS! 625 | ) 626 | 627 | if defined sbt_args_sbt_dir ( 628 | set _SBT_OPTS=-Dsbt.global.base=!sbt_args_sbt_dir! !_SBT_OPTS! 629 | ) 630 | 631 | if defined sbt_args_sbt_boot ( 632 | set _SBT_OPTS=-Dsbt.boot.directory=!sbt_args_sbt_boot! !_SBT_OPTS! 633 | ) 634 | 635 | if defined sbt_args_sbt_cache ( 636 | set _SBT_OPTS=-Dsbt.global.localcache=!sbt_args_sbt_cache! !_SBT_OPTS! 637 | ) 638 | 639 | if defined sbt_args_ivy ( 640 | set _SBT_OPTS=-Dsbt.ivy.home=!sbt_args_ivy! !_SBT_OPTS! 641 | ) 642 | 643 | if defined sbt_args_color ( 644 | set _SBT_OPTS=-Dsbt.color=!sbt_args_color! !_SBT_OPTS! 645 | ) 646 | 647 | if defined sbt_args_mem ( 648 | call :addMemory !sbt_args_mem! 649 | ) else ( 650 | call :addDefaultMemory 651 | ) 652 | 653 | if defined sbt_args_timings ( 654 | set _SBT_OPTS=-Dsbt.task.timings=true -Dsbt.task.timings.on.shutdown=true !_SBT_OPTS! 655 | ) 656 | 657 | if defined sbt_args_traces ( 658 | set _SBT_OPTS=-Dsbt.traces=true !_SBT_OPTS! 659 | ) 660 | 661 | if defined sbt_args_no_server ( 662 | set _SBT_OPTS=-Dsbt.io.virtual=false -Dsbt.server.autostart=false !_SBT_OPTS! 663 | ) 664 | 665 | rem TODO: _SBT_OPTS needs to be processed as args and diffed against SBT_ARGS 666 | 667 | if !sbt_args_print_sbt_version! equ 1 ( 668 | call :set_sbt_version 669 | echo !sbt_version! 670 | goto :eof 671 | ) 672 | 673 | if !sbt_args_print_version! equ 1 ( 674 | call :set_sbt_version 675 | echo sbt version in this project: !sbt_version! 676 | echo sbt script version: !init_sbt_version! 677 | goto :eof 678 | ) 679 | 680 | if defined sbt_args_verbose ( 681 | echo # Executing command line: 682 | echo "!_JAVACMD!" 683 | if defined _JAVA_OPTS ( call :echolist !_JAVA_OPTS! ) 684 | if defined _SBT_OPTS ( call :echolist !_SBT_OPTS! ) 685 | if defined JAVA_TOOL_OPTIONS ( call :echolist %JAVA_TOOL_OPTIONS% ) 686 | echo -cp 687 | echo "!sbt_jar!" 688 | echo xsbt.boot.Boot 689 | if not "%~1" == "" ( call :echolist %* ) 690 | echo. 691 | ) 692 | 693 | "!_JAVACMD!" !_JAVA_OPTS! !_SBT_OPTS! %JAVA_TOOL_OPTIONS% -cp "!sbt_jar!" xsbt.boot.Boot %* 694 | 695 | goto :eof 696 | 697 | :runnative 698 | 699 | set "_SBTNCMD=!SBT_BIN_DIR!sbtn-x86_64-pc-win32.exe" 700 | 701 | if defined sbt_args_verbose ( 702 | echo # running native client 703 | if not "%~1" == "" ( call :echolist %* ) 704 | set "SBT_ARGS=-v !SBT_ARGS!" 705 | ) 706 | 707 | set "SBT_SCRIPT=!SBT_BIN_DIR: =%%20!sbt.bat" 708 | set "SBT_ARGS=--sbt-script=!SBT_SCRIPT! %SBT_ARGS%" 709 | 710 | rem Microsoft Visual C++ 2010 SP1 Redistributable Package (x64) is required 711 | rem https://www.microsoft.com/en-us/download/details.aspx?id=13523 712 | "!_SBTNCMD!" %SBT_ARGS% 713 | 714 | goto :eof 715 | 716 | rem for expression tries to interpret files, so simply loop over %* instead 717 | rem fixes dealing with quotes after = args: -Dscala.ext.dirs="C:\Users\First Last\.sbt\0.13\java9-rt-ext-adoptopenjdk_11_0_3" 718 | :echolist 719 | rem call method is in first call of %0 720 | shift 721 | 722 | if "%~0" == "" goto echolist_end 723 | set "p=%~0" 724 | 725 | if "%p:~0,2%" == "-D" ( 726 | rem special handling for -D since '=' gets parsed away 727 | for /F "tokens=1 delims==" %%a in ("%p%") do ( 728 | rem make sure it doesn't have the '=' already 729 | if "%p%" == "%%a" if not "%~1" == "" ( 730 | echo %0=%1 731 | shift 732 | goto echolist 733 | ) 734 | ) 735 | ) 736 | 737 | if not "%p:~0,5%" == "-XX:+" if not "%p:~0,5%" == "-XX:-" if "%p:~0,3%" == "-XX" ( 738 | rem special handling for -XX since '=' gets parsed away 739 | for /F "tokens=1 delims==" %%a in ("%p%") do ( 740 | rem make sure it doesn't have the '=' already 741 | if "%p%" == "%%a" if not "%~1" == "" ( 742 | echo %0=%1 743 | shift 744 | goto echolist 745 | ) 746 | ) 747 | ) 748 | 749 | if "%p:~0,14%" == "-agentlib:jdwp" ( 750 | rem special handling for --jvm-debug since '=' and ',' gets parsed away 751 | for /F "tokens=1 delims==" %%a in ("%p%") do ( 752 | rem make sure it doesn't have the '=' already 753 | if "%p%" == "%%a" if not "%~1" == "" if not "%~2" == "" if not "%~3" == "" if not "%~4" == "" if not "%~5" == "" if not "%~6" == "" if not "%~7" == "" if not "%~8" == "" ( 754 | echo %0=%1=%2,%3=%4,%5=%6,%7=%8 755 | shift & shift & shift & shift & shift & shift & shift & shift 756 | goto echolist 757 | ) 758 | ) 759 | ) 760 | 761 | echo %0 762 | goto echolist 763 | 764 | :echolist_end 765 | 766 | exit /B 0 767 | 768 | :addJava 769 | call :dlog [addJava] arg = '%*' 770 | set "_JAVA_OPTS=!_JAVA_OPTS! %*" 771 | exit /B 0 772 | 773 | :addMemory 774 | call :dlog [addMemory] arg = '%*' 775 | 776 | rem evict memory related options 777 | set _new_java_opts= 778 | set _old_java_opts=!_JAVA_OPTS! 779 | :next_java_opt 780 | if "!_old_java_opts!" == "" goto :done_java_opt 781 | for /F "tokens=1,*" %%g in ("!_old_java_opts!") do ( 782 | set "p=%%g" 783 | if not "!p:~0,4!" == "-Xmx" if not "!p:~0,4!" == "-Xms" if not "!p:~0,4!" == "-Xss" if not "!p:~0,15!" == "-XX:MaxPermSize" if not "!p:~0,20!" == "-XX:MaxMetaspaceSize" if not "!p:~0,25!" == "-XX:ReservedCodeCacheSize" ( 784 | set _new_java_opts=!_new_java_opts! %%g 785 | ) 786 | set "_old_java_opts=%%h" 787 | ) 788 | goto :next_java_opt 789 | :done_java_opt 790 | set _JAVA_OPTS=!_new_java_opts! 791 | 792 | set _new_sbt_opts= 793 | set _old_sbt_opts=!_SBT_OPTS! 794 | :next_sbt_opt 795 | if "!_old_sbt_opts!" == "" goto :done_sbt_opt 796 | for /F "tokens=1,*" %%g in ("!_old_sbt_opts!") do ( 797 | set "p=%%g" 798 | if not "!p:~0,4!" == "-Xmx" if not "!p:~0,4!" == "-Xms" if not "!p:~0,4!" == "-Xss" if not "!p:~0,15!" == "-XX:MaxPermSize" if not "!p:~0,20!" == "-XX:MaxMetaspaceSize" if not "!p:~0,25!" == "-XX:ReservedCodeCacheSize" ( 799 | set _new_sbt_opts=!_new_sbt_opts! %%g 800 | ) 801 | set "_old_sbt_opts=%%h" 802 | ) 803 | goto :next_sbt_opt 804 | :done_sbt_opt 805 | set _SBT_OPTS=!_new_sbt_opts! 806 | 807 | rem a ham-fisted attempt to move some memory settings in concert 808 | set mem=%1 809 | set /a codecache=!mem! / 8 810 | if !codecache! GEQ 512 set /a codecache=512 811 | if !codecache! LEQ 128 set /a codecache=128 812 | 813 | set /a class_metadata_size=!codecache! * 2 814 | 815 | call :addJava -Xms!mem!m 816 | call :addJava -Xmx!mem!m 817 | call :addJava -Xss4M 818 | call :addJava -XX:ReservedCodeCacheSize=!codecache!m 819 | 820 | if /I !JAVA_VERSION! LSS 8 ( 821 | call :addJava -XX:MaxPermSize=!class_metadata_size!m 822 | ) 823 | 824 | exit /B 0 825 | 826 | :addDefaultMemory 827 | rem if we detect any of these settings in ${JAVA_OPTS} or ${JAVA_TOOL_OPTIONS} we need to NOT output our settings. 828 | rem The reason is the Xms/Xmx, if they don't line up, cause errors. 829 | 830 | set _has_memory_args= 831 | 832 | if defined _JAVA_OPTS for %%g in (%_JAVA_OPTS%) do ( 833 | set "p=%%g" 834 | if "!p:~0,4!" == "-Xmx" set _has_memory_args=1 835 | if "!p:~0,4!" == "-Xms" set _has_memory_args=1 836 | if "!p:~0,4!" == "-Xss" set _has_memory_args=1 837 | ) 838 | 839 | if defined JAVA_TOOL_OPTIONS for %%g in (%JAVA_TOOL_OPTIONS%) do ( 840 | set "p=%%g" 841 | if "!p:~0,4!" == "-Xmx" set _has_memory_args=1 842 | if "!p:~0,4!" == "-Xms" set _has_memory_args=1 843 | if "!p:~0,4!" == "-Xss" set _has_memory_args=1 844 | ) 845 | 846 | if defined _SBT_OPTS for %%g in (%_SBT_OPTS%) do ( 847 | set "p=%%g" 848 | if "!p:~0,4!" == "-Xmx" set _has_memory_args=1 849 | if "!p:~0,4!" == "-Xms" set _has_memory_args=1 850 | if "!p:~0,4!" == "-Xss" set _has_memory_args=1 851 | ) 852 | 853 | if not defined _has_memory_args ( 854 | call :addMemory !sbt_default_mem! 855 | ) 856 | exit /B 0 857 | 858 | :dlog 859 | if defined sbt_args_debug ( 860 | echo %* 1>&2 861 | ) 862 | exit /B 0 863 | 864 | :process 865 | rem Parses x out of 1.x; for example 8 out of java version 1.8.0_xx 866 | rem Otherwise, parses the major version; 9 out of java version 9-ea 867 | set JAVA_VERSION=0 868 | 869 | for /f "tokens=3 usebackq" %%g in (`CALL "!_JAVACMD!" -Xms32M -Xmx32M -version 2^>^&1 ^| findstr /i version`) do ( 870 | set JAVA_VERSION=%%g 871 | ) 872 | 873 | rem removes all quotes from JAVA_VERSION 874 | set JAVA_VERSION=!JAVA_VERSION:"=! 875 | 876 | for /f "delims=.-_ tokens=1-2" %%v in ("!JAVA_VERSION!") do ( 877 | if /I "%%v" EQU "1" ( 878 | set JAVA_VERSION=%%w 879 | ) else ( 880 | set JAVA_VERSION=%%v 881 | ) 882 | ) 883 | 884 | rem parse the first two segments of sbt.version and set run_native_client to 885 | rem 1 if the user has also indicated they want to use native client. 886 | set sbtV=!build_props_sbt_version! 887 | set sbtBinaryV_1= 888 | set sbtBinaryV_2= 889 | for /F "delims=.-_ tokens=1-2" %%v in ("!sbtV!") do ( 890 | set sbtBinaryV_1=%%v 891 | set sbtBinaryV_2=%%w 892 | ) 893 | set native_client_ready= 894 | if !sbtBinaryV_1! geq 2 ( 895 | set native_client_ready=1 896 | ) else ( 897 | if !sbtBinaryV_1! geq 1 ( 898 | if !sbtBinaryV_2! geq 4 ( 899 | set native_client_ready=1 900 | ) 901 | ) 902 | ) 903 | if !native_client_ready! equ 1 ( 904 | if !sbt_args_client! equ 1 ( 905 | set run_native_client=1 906 | ) 907 | ) 908 | set native_client_ready= 909 | 910 | exit /B 0 911 | 912 | :checkjava 913 | set /a required_version=6 914 | if /I !JAVA_VERSION! GEQ !required_version! ( 915 | exit /B 0 916 | ) 917 | echo. 918 | echo The Java Development Kit ^(JDK^) installation you have is not up to date. 919 | echo sbt requires at least version !required_version!+, you have 920 | echo version "!JAVA_VERSION!" 921 | echo. 922 | echo Please go to http://www.oracle.com/technetwork/java/javase/downloads/ and download 923 | echo a valid JDK and install before running sbt. 924 | echo. 925 | exit /B 1 926 | 927 | :copyrt 928 | if /I !JAVA_VERSION! GEQ 9 ( 929 | "!_JAVACMD!" !_JAVA_OPTS! !_SBT_OPTS! -jar "!sbt_jar!" --rt-ext-dir > "%TEMP%.\rtext.txt" 930 | set /p java9_ext= < "%TEMP%.\rtext.txt" 931 | set "java9_rt=!java9_ext!\rt.jar" 932 | 933 | if not exist "!java9_rt!" ( 934 | mkdir "!java9_ext!" 935 | "!_JAVACMD!" !_JAVA_OPTS! !_SBT_OPTS! -jar "!sbt_jar!" --export-rt "!java9_rt!" 936 | ) 937 | set _JAVA_OPTS=!_JAVA_OPTS! -Dscala.ext.dirs="!java9_ext!" 938 | ) 939 | exit /B 0 940 | 941 | :sync_preloaded 942 | if not defined init_sbt_version ( 943 | rem FIXME: better !init_sbt_version! detection 944 | FOR /F "tokens=* usebackq" %%F IN (`dir /b "!SBT_HOME!\lib\local-preloaded\org\scala-sbt\sbt" /B`) DO ( 945 | SET init_sbt_version=%%F 946 | ) 947 | ) 948 | 949 | set PRELOAD_SBT_JAR="%UserProfile%\.sbt\preloaded\org\scala-sbt\sbt\!init_sbt_version!\" 950 | if /I !JAVA_VERSION! GEQ 8 ( 951 | where robocopy >nul 2>nul 952 | if %ERRORLEVEL% EQU 0 ( 953 | if not exist !PRELOAD_SBT_JAR! ( 954 | if exist "!SBT_HOME!\lib\local-preloaded\" ( 955 | robocopy "!SBT_HOME!\lib\local-preloaded" "%UserProfile%\.sbt\preloaded" /E >nul 2>nul 956 | ) 957 | ) 958 | ) 959 | ) 960 | exit /B 0 961 | 962 | :usage 963 | 964 | for /f "tokens=3 usebackq" %%g in (`CALL "!_JAVACMD!" -Xms32M -Xmx32M -version 2^>^&1 ^| findstr /i version`) do ( 965 | set FULL_JAVA_VERSION=%%g 966 | ) 967 | 968 | echo. 969 | echo Usage: %~n0 [options] 970 | echo. 971 | echo -h ^| --help print this message 972 | echo -v ^| --verbose this runner is chattier 973 | echo -V ^| --version print sbt version information 974 | echo --numeric-version print the numeric sbt version (sbt sbtVersion) 975 | echo --script-version print the version of sbt script 976 | echo -d ^| --debug set sbt log level to debug 977 | echo -debug-inc ^| --debug-inc 978 | echo enable extra debugging for the incremental debugger 979 | echo --no-colors disable ANSI color codes 980 | echo --color=auto^|always^|true^|false^|never 981 | echo enable or disable ANSI color codes ^(sbt 1.3 and above^) 982 | echo --supershell=auto^|always^|true^|false^|never 983 | echo enable or disable supershell ^(sbt 1.3 and above^) 984 | echo --traces generate Trace Event report on shutdown ^(sbt 1.3 and above^) 985 | echo --timings display task timings report on shutdown 986 | echo --sbt-create start sbt even if current directory contains no sbt project 987 | echo --sbt-dir ^ path to global settings/plugins directory ^(default: ~/.sbt^) 988 | echo --sbt-boot ^ path to shared boot directory ^(default: ~/.sbt/boot in 0.11 series^) 989 | echo --sbt-cache ^ path to global cache directory ^(default: operating system specific^) 990 | echo --ivy ^ path to local Ivy repository ^(default: ~/.ivy2^) 991 | echo --mem ^ set memory options ^(default: %sbt_default_mem%^) 992 | echo --no-share use all local caches; no sharing 993 | echo --no-global uses global caches, but does not use global ~/.sbt directory. 994 | echo --jvm-debug ^ enable on JVM debugging, open at the given port. 995 | rem echo --batch disable interactive mode 996 | echo. 997 | echo # sbt version ^(default: from project/build.properties if present, else latest release^) 998 | echo --sbt-version ^ use the specified version of sbt 999 | echo --sbt-jar ^ use the specified jar as the sbt launcher 1000 | echo. 1001 | echo # java version ^(default: java from PATH, currently !FULL_JAVA_VERSION!^) 1002 | echo --java-home ^ alternate JAVA_HOME 1003 | echo. 1004 | echo # jvm options and output control 1005 | echo JAVA_OPTS environment variable, if unset uses "!default_java_opts!" 1006 | echo .jvmopts if this file exists in the current directory, its contents 1007 | echo are appended to JAVA_OPTS 1008 | echo SBT_OPTS environment variable, if unset uses "!default_sbt_opts!" 1009 | echo .sbtopts if this file exists in the current directory, its contents 1010 | echo are prepended to the runner args 1011 | echo !SBT_CONFIG! 1012 | echo if this file exists, it is prepended to the runner args 1013 | echo -Dkey=val pass -Dkey=val directly to the java runtime 1014 | rem echo -J-X pass option -X directly to the java runtime 1015 | rem echo ^(-J is stripped^) 1016 | rem echo -S-X add -X to sbt's scalacOptions ^(-S is stripped^) 1017 | echo. 1018 | echo In the case of duplicated or conflicting options, the order above 1019 | echo shows precedence: JAVA_OPTS lowest, command line options highest. 1020 | echo. 1021 | 1022 | @endlocal 1023 | exit /B 1 1024 | 1025 | :set_sbt_version 1026 | rem set project sbtVersion 1027 | for /F "usebackq tokens=2" %%G in (`CALL "!_JAVACMD!" -jar "!sbt_jar!" "sbtVersion" 2^>^&1`) do set "sbt_version=%%G" 1028 | exit /B 0 1029 | 1030 | :error 1031 | @endlocal 1032 | exit /B 1 1033 | 1034 | :end 1035 | @endlocal 1036 | exit /B 0 1037 | --------------------------------------------------------------------------------