-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathInstallationManager.kt
More file actions
255 lines (225 loc) · 9.34 KB
/
Copy pathInstallationManager.kt
File metadata and controls
255 lines (225 loc) · 9.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
package global
import file.Download
import file.ZipArchiveExtractor
import file.clearFolder
import logging.KotlinLogging
import net.ConnectionManager
import net.NetStatus
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.util.jar.JarFile
import java.util.regex.Pattern
/**
* Manages the global Wurst installation located inside the ~/.wurst directory
*/
object InstallationManager {
private val log = KotlinLogging.logger {}
private const val FOLDER_PATH = ".wurst"
private const val COMPILER_FILE_NAME = "wurstscript.jar"
private const val LANGUAGE_AGENT_DOC_ENTRY = "agent-docs/WURST_LANGUAGE.md"
private const val GRILL_JAR_NAME = "grill.jar"
private const val LEGACY_GRILL_JAR_NAME = "WurstSetup.jar"
val installDir: Path get() = configuredInstallDir()
val compilerDir: Path get() = installDir.resolve("wurst-compiler")
val runtimeDir: Path get() = installDir.resolve("wurst-runtime")
val grillDir: Path get() = installDir.resolve("grill-cli")
val compilerJar: Path get() = compilerDir.resolve(COMPILER_FILE_NAME)
val legacyCompilerJar: Path get() = installDir.resolve(COMPILER_FILE_NAME)
val grillJar: Path get() = grillDir.resolve(GRILL_JAR_NAME)
val legacyGrillJar: Path get() = installDir.resolve(LEGACY_GRILL_JAR_NAME)
val wurstscriptLauncher: Path get() = installDir.resolve(if (isWindows()) "wurstscript.cmd" else "wurstscript")
val grillLauncher: Path get() = installDir.resolve(if (isWindows()) "grill.cmd" else "grill")
val bundledJava: Path get() = runtimeDir.resolve("bin").resolve(if (isWindows()) "java.exe" else "java")
var wurstConfig: WurstConfigData? = null
var status = InstallationStatus.NOT_INSTALLED
var currentCompilerVersion = -1
var latestCompilerVersion = 0
fun verifyInstallation(probeVersion: Boolean = true): InstallationStatus {
log.debug("verify Install")
status = InstallationStatus.NOT_INSTALLED
currentCompilerVersion = -1
latestCompilerVersion = 0
val detectedCompilerJar = detectCompilerJar()
log.info("verifyInstallation: detectedCompilerJar=$detectedCompilerJar exists=${detectedCompilerJar?.let { Files.exists(it) }}")
if (detectedCompilerJar != null) {
log.info("Found installation at $detectedCompilerJar")
ensureCompilerAgentDocs(detectedCompilerJar)
status = InstallationStatus.INSTALLED_UNKNOWN
try {
if (!Files.isWritable(detectedCompilerJar)) {
CLIParser.showWurstInUse()
} else if (probeVersion) {
CLIParser.getVersionFomJar()
}
} catch (_: Error) {
log.warn("Custom WurstScript installation detected.")
}
} else {
log.info("WurstScript is not currently installed (no compiler jar found at $compilerJar or $legacyCompilerJar).")
}
if (probeVersion && ConnectionManager.netStatus == NetStatus.ONLINE) {
log.debug("Client online, check for update")
latestCompilerVersion = ConnectionManager.getLatestCompilerBuild()
log.debug("latest compiler: $latestCompilerVersion")
if (currentCompilerVersion >= 0 && latestCompilerVersion > 0 && currentCompilerVersion >= latestCompilerVersion) {
status = InstallationStatus.INSTALLED_UPTODATE
}
} else {
log.debug("Client offline, check for update")
}
return status
}
fun handleUpdate() {
val isFreshInstall = status == InstallationStatus.NOT_INSTALLED
try {
log.debug(if (isFreshInstall) "isInstall" else "isUpdate")
log.info("⏬ Downloading WurstScript..")
downloadCompiler(isFreshInstall)
} catch (e: Exception) {
log.error("Exception: ", e)
Log.print("\n===ERROR COMPILER UPDATE===\n" + e.message + "\nPlease report here: github.com/wurstscript/WurstScript/issues\n")
}
}
private fun downloadCompiler(isFreshInstall: Boolean) {
Download.downloadCompiler {
log.info("\t📦 Extracting..")
ZipArchiveExtractor.extractArchive(it, installDir)
Files.delete(it)
val compilerJar = detectCompilerJar()
if (compilerJar == null) {
log.error("❌ Compiler not found after extraction.")
} else {
ensureCompilerAgentDocs(compilerJar)
if (isFreshInstall) { wurstConfig = WurstConfigData() }
ensureGrillJarInstalled()
setLaunchersExecutable()
log.info("✔ Installed WurstScript to $installDir")
}
}
}
private fun setLaunchersExecutable() {
try {
installDir.resolve("grill").toFile().setExecutable(true)
installDir.resolve("wurstscript").toFile().setExecutable(true)
bundledJava.toFile().setExecutable(true)
} catch (_: Exception) {
}
}
fun ensureGrillJarInstalled() {
val ownJar = resolveOwnJar() ?: return
Files.createDirectories(grillDir)
if (ownJar != grillJar) {
Files.copy(ownJar, grillJar, java.nio.file.StandardCopyOption.REPLACE_EXISTING)
}
try {
Files.deleteIfExists(legacyGrillJar)
} catch (_: Exception) {
}
}
private val jenkinsVerPattern = Pattern.compile("""(?:\d\.){3}\d(?:-\w+)+-(\d+)""")
fun isJenkinsBuilt(version: String): Boolean {
return jenkinsVerPattern.matcher(version).matches()
}
fun getJenkinsBuildVer(version: String): Int {
val matcher = jenkinsVerPattern.matcher(version)
if (matcher.matches()) {
return matcher.group(1).toInt()
}
return 0
}
fun handleRemove() {
val jarInUse = detectCompilerJar()?.let { !Files.isWritable(it) } ?: false
if (jarInUse) {
log.error("❌ Cannot remove WurstScript: compiler jar is in use. Close VSCode and any running Wurst instances first.")
return
}
removeCompilerInstall()
verifyInstallation()
log.info("WurstScript has been removed.")
}
private fun removeCompilerInstall() {
clearFolder(compilerDir)
tryDelete(compilerDir)
tryDelete(legacyCompilerJar)
tryDelete(wurstscriptLauncher)
}
private fun tryDelete(path: Path) {
try {
Files.deleteIfExists(path)
} catch (e: Exception) {
log.warn("Could not remove $path: ${e.message}")
}
}
fun getCompilerPath(): String {
return (detectCompilerJar() ?: compilerJar).toAbsolutePath().toString()
}
fun compilerLaunchCommand(vararg extraArgs: String): Array<String> {
val compiler = detectCompilerJar() ?: compilerJar
val java = bundledJavaCommand()
return arrayOf(java, "-jar", compiler.toAbsolutePath().toString(), *extraArgs)
}
private fun bundledJavaCommand(): String {
return if (Files.exists(bundledJava) && Files.isExecutable(bundledJava)) {
bundledJava.toAbsolutePath().toString()
} else {
"java"
}
}
private fun detectCompilerJar(): Path? {
return when {
Files.exists(compilerJar) -> compilerJar
Files.exists(legacyCompilerJar) -> legacyCompilerJar
else -> null
}
}
private fun ensureCompilerAgentDocs(compilerJar: Path) {
try {
JarFile(compilerJar.toFile()).use { jar ->
val docsDir = compilerDir.resolve("agent-docs")
val docsFile = docsDir.resolve("WURST_LANGUAGE.md")
val entry = jar.getJarEntry(LANGUAGE_AGENT_DOC_ENTRY)
if (entry == null) {
// A compiler downgrade may remove the resource. Do not leave docs from the old compiler
// in place, or generated projects could use language guidance for the wrong version.
Files.deleteIfExists(docsFile)
return
}
Files.createDirectories(docsDir)
jar.getInputStream(entry).use { input ->
Files.copy(
input,
docsFile,
StandardCopyOption.REPLACE_EXISTING
)
}
}
} catch (e: Exception) {
log.warn("Could not extract compiler agent docs: ${e.message}")
}
}
private fun resolveOwnJar(): Path? {
return try {
val url = InstallationManager::class.java.protectionDomain.codeSource.location
val ownFile = Paths.get(url.toURI())
if (Files.exists(ownFile) && ownFile.toString().endsWith(".jar")) ownFile else null
} catch (_: Exception) {
null
}
}
private fun configuredInstallDir(): Path {
val override = System.getProperty("wurst.install.dir").orEmpty().trim()
if (override.isNotEmpty()) {
return Paths.get(override)
}
return Paths.get(System.getProperty("user.home"), FOLDER_PATH)
}
private fun isWindows(): Boolean = System.getProperty("os.name").contains("windows", true)
enum class InstallationStatus {
NOT_INSTALLED,
INSTALLED_UNKNOWN,
INSTALLED_OUTDATED,
INSTALLED_UPTODATE
}
}