Skip to content

Commit c95bc67

Browse files
committed
write a build daemon unidocd
1 parent 2303de6 commit c95bc67

File tree

4 files changed

+203
-6
lines changed

4 files changed

+203
-6
lines changed

.github/workflows/Deploy.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,12 @@ jobs:
113113
Scripts/Linux/Build ${{ matrix.arch.id }} \
114114
--os ${{ matrix.os.codename }}
115115
Scripts/Package .build.${{ matrix.arch.id }} \
116-
unidoc-linkerd
116+
unidoc-linkerd \
117+
unidocd
117118
118119
- name: Upload products
119120
env:
120121
UNIDOC_PLATFORM: "${{ matrix.os.version }}-${{ matrix.arch.name }}"
121122
UNIDOC_VERSION: ${{ github.head_ref || github.ref_name }}
122123

123-
run: Scripts/Deploy unidoc-linkerd.tar.gz
124+
run: Scripts/Deploy unidoc-linkerd.tar.gz unidocd.tar.gz

Package.swift

+8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ let package:Package = .init(
1010
.executable(name: "ssgc", targets: ["ssgc"]),
1111
.executable(name: "unidoc", targets: ["unidoc-tools"]),
1212
.executable(name: "unidoc-linkerd", targets: ["unidoc-linkerd"]),
13+
.executable(name: "unidocd", targets: ["unidocd"]),
1314

1415
.library(name: "guides", targets: ["guides"]),
1516

@@ -137,6 +138,13 @@ let package:Package = .init(
137138
.target(name: "UnidocLinkerPlugin"),
138139
]),
139140

141+
.executableTarget(name: "unidocd",
142+
dependencies: [
143+
.target(name: "UnidocClient"),
144+
.product(name: "System_ArgumentParser", package: "swift-io"),
145+
.product(name: "UnixCalendar", package: "swift-unixtime"),
146+
]),
147+
140148

141149
.target(name: "_AsyncChannel",
142150
dependencies: [

Scripts/Package

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -e
44
SWIFTPM_SCRATCH=$1
55

66
if [ -z $SWIFTPM_SCRATCH ]; then
7-
echo "Usage: $0 <scratch-path> [ unidoc | unidoc-linkerd ]"
7+
echo "Usage: $0 <scratch-path> [ unidoc | unidoc-linkerd | unidocd ]"
88
exit 1
99
else
1010
shift
@@ -20,10 +20,10 @@ while [[ $# -gt 0 ]]; do
2020
-C $SWIFTPM_SCRATCH/release unidoc
2121
;;
2222

23-
unidoc-linkerd )
23+
unidoc-linkerd | unidocd )
24+
tar -czf $1.tar.gz \
25+
-C $SWIFTPM_SCRATCH/release $1
2426
shift
25-
tar -czf unidoc-linkerd.tar.gz \
26-
-C $SWIFTPM_SCRATCH/release unidoc-linkerd
2727
;;
2828

2929
* )

Sources/unidocd/Main.swift

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#if canImport(Darwin)
2+
@preconcurrency import Darwin
3+
#elseif canImport(Glibc)
4+
@preconcurrency import Glibc
5+
#endif
6+
7+
import ArgumentParser
8+
import HTTPClient
9+
import NIOCore
10+
import NIOSSL
11+
import Symbols
12+
import SystemIO
13+
import SymbolGraphCompiler
14+
import UnidocClient
15+
16+
struct Main
17+
{
18+
@Option(
19+
name: [.customLong("swiftinit"), .customShort("S")],
20+
help: """
21+
The API key for the Unidoc server running on swiftinit.org, \
22+
also sets the host and port to the production values
23+
""")
24+
var authorizationSwiftinit:String?
25+
26+
@Option(
27+
name: [.customLong("authorization"), .customShort("i")],
28+
help: "The API key for the Unidoc server")
29+
var authorization:String?
30+
31+
@Option(
32+
name: [.customLong("host"), .customShort("h")],
33+
help: "The name of a host running a compatible instance of unidoc")
34+
var host:String = "localhost"
35+
36+
@Option(
37+
name: [.customLong("port"), .customShort("p")],
38+
help: "The number of a port bound to a compatible instance of unidoc")
39+
var port:Int = 8443
40+
41+
@Option(
42+
name: [.customLong("swift-toolchain"), .customShort("u")],
43+
help: "The path to a Swift toolchain directory, usually ending in 'usr'",
44+
completion: .directory)
45+
var toolchain:FilePath.Directory = "/home/ubuntu/6.0.3/aarch64/usr"
46+
47+
@Option(
48+
name: [.customLong("swift-sdk"), .customShort("k")],
49+
help: "The Swift SDK to use")
50+
var sdk:SSGC.AppleSDK?
51+
52+
@Flag(
53+
name: [.customLong("pretty"), .customShort("o")],
54+
help: "Tell lib/SymbolGraphGen to pretty-print the JSON output, if possible")
55+
var pretty:Bool = false
56+
57+
@Flag(
58+
name: [.customLong("init-stdlib")],
59+
help: "Generate and upload the standard library documentation")
60+
var initStandardLibrary:Bool = false
61+
}
62+
extension Main
63+
{
64+
private mutating
65+
func normalize()
66+
{
67+
if let authorization:String = self.authorizationSwiftinit,
68+
case nil = self.authorization
69+
{
70+
self.authorization = authorization
71+
self.host = "swiftinit.org"
72+
self.port = 443
73+
}
74+
75+
#if os(macOS)
76+
77+
// Guess the SDK if not specified.
78+
self.sdk = self.sdk ?? .macOS
79+
80+
#endif
81+
}
82+
83+
private
84+
var client:Unidoc.Client<HTTP.Client2>
85+
{
86+
get throws
87+
{
88+
var configuration:TLSConfiguration = .makeClientConfiguration()
89+
configuration.applicationProtocols = ["h2"]
90+
91+
// If we are not using the default port, we are probably running locally.
92+
if self.port != 443
93+
{
94+
configuration.certificateVerification = .none
95+
}
96+
97+
return .init(authorization: self.authorization,
98+
pretty: self.pretty,
99+
http: .init(threads: .singleton,
100+
niossl: try .init(configuration: configuration),
101+
remote: self.host),
102+
port: self.port)
103+
}
104+
}
105+
106+
private
107+
var triple:Symbol.Triple
108+
{
109+
get throws
110+
{
111+
let tools:SSGC.Toolchain.Paths = .init(swiftPM: nil, usr: self.toolchain)
112+
let splash:SSGC.Toolchain.Splash = try .init(running: tools.swiftCommand)
113+
return splash.triple
114+
}
115+
}
116+
}
117+
@main
118+
extension Main:AsyncParsableCommand
119+
{
120+
static var configuration:CommandConfiguration { .init(commandName: "unidocd") }
121+
122+
mutating
123+
func run() async throws
124+
{
125+
self.normalize()
126+
127+
NIOSingletons.groupLoopCountSuggestion = 2
128+
setlinebuf(stdout)
129+
130+
let unidoc:Unidoc.Client<HTTP.Client2> = try self.client
131+
let triple:Symbol.Triple = try self.triple
132+
let cache:FilePath = "swiftpm"
133+
134+
print("Connecting to \(self.host):\(self.port)...")
135+
136+
if self.initStandardLibrary
137+
{
138+
let toolchain:Unidoc.Toolchain = .init(usr: self.toolchain, sdk: self.sdk)
139+
try await unidoc.buildAndUpload(local: nil,
140+
name: "swift",
141+
type: .package,
142+
with: toolchain)
143+
}
144+
145+
while true
146+
{
147+
// Don’t run too hot if the network is down.
148+
async
149+
let cooldown:Void = try await Task.sleep(for: .seconds(5))
150+
151+
do
152+
{
153+
let labels:Unidoc.BuildLabels? = try await unidoc.connect
154+
{
155+
try await $0.subscribe(to: triple)
156+
}
157+
158+
if let labels:Unidoc.BuildLabels
159+
{
160+
print("""
161+
Building package '\(labels.package)' at '\(labels.ref)' \
162+
(\(labels.coordinate))
163+
""")
164+
165+
let toolchain:Unidoc.Toolchain = .init(usr: self.toolchain, sdk: self.sdk)
166+
/// As this runs continuously, we should remove the build artifacts
167+
/// afterwards, to avoid filling up the disk. We must also remove the cloned
168+
/// repository, as it may experience name conflicts on long timescales.
169+
try await unidoc.buildAndUpload(
170+
labels: labels,
171+
remove: true,
172+
with: toolchain,
173+
cache: cache)
174+
}
175+
else
176+
{
177+
print("Heartbeat received; no packages to build.")
178+
}
179+
}
180+
catch let error
181+
{
182+
print("Error: \(error)")
183+
}
184+
185+
try await cooldown
186+
}
187+
}
188+
}

0 commit comments

Comments
 (0)