[2.x] doc: tech stack (#8609)

Problem/Solution

Document sbt's tech stack.
This commit is contained in:
eugene yokota 2026-01-22 04:20:06 -05:00 committed by GitHub
parent f4ab500d19
commit 089d56c50e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 153 additions and 0 deletions

View File

@ -38,6 +38,11 @@ For changes that require coordination with file changes and tasks, use scripted
- [contributing-docs/05_scripted_tests.md](contributing-docs/05_scripted_tests.md)
- [contributing-docs/06_manual_tests.md](contributing-docs/06_manual_tests.md)
Tech stack
----------
- [contributing-docs/07_tech_stack.md](contribution-docs/07_tech_stach.md)
Binary compatibility
--------------------

View File

@ -90,6 +90,12 @@ sbt features a testing infrastructure encompassing multiple testing methodologie
- [scripted tests](05_scripted_tests.md)
- [Manual tests](06_manual_tests.md), using the locally baked sbt
### Tech stack
sbt code base uses a set of libraries and tooling, sometimes unique to sbt:
- [Tech stack](07_tech_stack.md)
### Instruction to build just sbt
sbt has a number of sub-modules. If the change you are making is just contained in sbt/sbt (not one of the sub-modules),

View File

@ -0,0 +1,142 @@
Tech stack
==========
This page explains some of the libraries and tooling used in the code base.
Testing
-------
See the previous sections for test libraries:
- [Unit tests](04_unit_tests.md)
- [scripted tests](05_scripted_tests.md)
sbt
---
The main features of sbt, such as compilation and testing are implemented
using the sbt's settings and task DSL as well as its submodules like IO.
Generally this means that user-facing and immutable configurations
should be expressed as settings, and side effects should be wrapped in a task.
Coursier for dependency management
----------------------------------
Since sbt 1.3, we have switched its library dependency management engine
from Apache Ivy to [Coursier][coursier].
Contraband for datatype
-----------------------
[Contraband][contraband] is a datatype description language that can generate:
- pseudo case classes in Scala or Java
- JSON bindings
```graphql
## Position in a text document expressed as zero-based line and zero-based character offset.
## A position is between two characters like an 'insert' cursor in a editor.
type Position {
## Line position in a document (zero-based).
line: Long!
## Character offset on a line in a document (zero-based).
character: Long!
}
## A range in a text document expressed as (zero-based) start and end positions. A range is comparable to a selection in an editor.
## Therefore the end position is exclusive.
type Range {
## The range's start position.
start: sbt.internal.bsp.Position!
## The range's end position.
end: sbt.internal.bsp.Position!
}
````
Unlike Scala's case classes, Contraband's pseudo case class can evolve over time without breaking binary compatibility of the generated Scala code.
sjson-new, Jawn, and SLIP-28 ScalaJson for JSON
-----------------------------------------------
For JSON serialization, sbt uses [sjson-new][sjson-new], a typeclass-based JSON codec library.
It offers `JsonFormat[A1]` typeclass, which lets you define how a datatype should be
broken down, but it does not specify which JSON AST should be used (backend-independent).
If you used Contraband for data, it can generate sjson-new codec for you.
Jawn is a fast JSON parsing library, that's also backend-independent.
As the concrete JSON AST, sbt adopts SLIP-28 [ScalaJSON][scalajson], a reference implementation of
a proposed effort to create a standard JSON AST. We also forked and shaded it, so it wouldn't clash
if the API changed over time.
Gigahorse for HTTP
------------------
[Gigahorse][gigahorse] is a backend-independent HTTP library sbt uses for some features.
As the concete backend we use Apache HttpClient 5.x.
```scala
private val http = {
val defaultHttpRequestTimeout = 2.minutes
val gigahorseConfig = Gigahorse.config
.withRequestTimeout(defaultHttpRequestTimeout)
.withReadTimeout(defaultHttpRequestTimeout)
Gigahorse.http(gigahorseConfig)
}
val req = Gigahorse
.url(s"${baseUrl}/publisher/upload?$q")
.post(
MultipartFormBody(
FormPart("bundle", bundleZipPath.toFile())
)
)
http.run(reqTransform(req), Gigahorse.asString)
http.close()
```
Putting them all together
-------------------------
Sonatype Publisher API combines Contraband, Jawn, sjson-new, and Gigahorse to access Sonatype's RESTful API.
First, one of its HTTP API is described as a Contraband type:
```graphql
type PublisherStatus {
deploymentId: String!
deploymentName: String!
deploymentState: sbt.internal.sona.DeploymentState!
purls: [String]
# Optional errors.
errors: sjsonnew.shaded.scalajson.ast.unsafe.JValue
}
```
Next, the HTTP endpoint is described as a Gigahorse request.
Finally the HTTP body is transformed via Jawn and sjson-new asynchronously in `http.run(...)`.
```scala
/**
* https://central.sonatype.org/publish/publish-portal-api/#verify-status-of-the-deployment
*/
private def deploymentStatusF(deploymentId: String): Future[PublisherStatus] = {
val req = Gigahorse
.url(s"${baseUrl}/publisher/status")
.addQueryString("id" -> deploymentId)
.post("", StandardCharsets.UTF_8)
http.run(reqTransform(req), SonaClient.asPublisherStatus)
}
```
[coursier]: https://github.com/coursier/coursier
[contraband]: https://www.scala-sbt.org/contraband/
[sjson-new]: https://github.com/eed3si9n/sjson-new
[scalajson]: https://github.com/sbt/scalajson
[jawn]: https://github.com/typelevel/jawn
[gigahorse]: https://eed3si9n.com/gigahorse/