Gradle, NPM, windows: creating symlink without administrator privilege

#KT-31694 Fixed
This commit is contained in:
Sergey Rostov
2019-05-31 13:07:00 +03:00
parent 85adee9804
commit fbefa17337
@@ -5,15 +5,19 @@
package org.jetbrains.kotlin.gradle.targets.js.npm
import com.google.gson.stream.JsonWriter
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import java.io.ByteArrayInputStream
import java.io.File
import java.io.StringWriter
import java.nio.file.Files
/**
* Handles simple case, when there are no npm depenencies required, but some tasks steel needs
* node_modules with symlinked packages and packages_imported
*/
class NpmSimpleLinker(rootProject: Project) {
class NpmSimpleLinker(val rootProject: Project) {
private val rootProjectNodeModules = rootProject.nodeJs.root.rootPackageDir.resolve(NpmProject.NODE_MODULES)
fun link(projects: Collection<NpmProjectPackage>) {
@@ -29,19 +33,71 @@ class NpmSimpleLinker(rootProject: Project) {
rootProjectNodeModules.mkdirs()
val fsLinker = createFSLinker()
// packages
projects.forEach {
Files.createSymbolicLink(getNodeModulePath(it.npmProject.name), it.npmProject.dir.canonicalFile.toPath())
fsLinker.link(getNodeModulePath(it.npmProject.name), it.npmProject.dir.canonicalFile)
}
// packages_imported
projects.flatMapTo(mutableSetOf()) {
it.gradleDependencies.externalModules.map { gradleNodeModule -> gradleNodeModule }
}.forEach {
Files.createSymbolicLink(getNodeModulePath(it.name), it.path.canonicalFile.toPath())
fsLinker.link(getNodeModulePath(it.name), it.path.canonicalFile)
}
fsLinker.execute()
}
private fun getNodeModulePath(name: String) = rootProjectNodeModules.resolve(name).canonicalFile
interface FSLinker {
fun link(linkFile: File, target: File)
fun execute() = Unit
}
fun createFSLinker(): FSLinker =
if (rootProject.nodeJs.root.environment.isWindows) WindowsJunctionFsLinker(rootProject, rootProjectNodeModules)
else JavaFsLinker()
class WindowsJunctionFsLinker(val project: Project, val dir: File) : FSLinker {
val js = StringBuilder()
init {
js.appendln("var fs = require('fs')")
}
fun addLink(linkFile: String, target: String) {
js.appendln("fs.symlinkSync(${jsQuotedString(linkFile)}, ${jsQuotedString(target)}, 'junction')")
}
private fun jsQuotedString(str: String) = StringWriter().also {
JsonWriter(it).value(str)
}.toString()
override fun link(linkFile: File, target: File) {
addLink(linkFile.absolutePath, target.absolutePath)
}
override fun execute() {
val result = project.exec { exec ->
exec.workingDir = dir
exec.executable = project.nodeJs.root.environment.nodeExecutable
exec.standardInput = ByteArrayInputStream(js.toString().toByteArray())
exec.standardOutput = System.`out`
exec.errorOutput = System.err
}
if (result.exitValue != 0) {
error("Cannot create window junctions: nodejs exited with ${result.exitValue}")
}
}
}
private fun getNodeModulePath(name: String) =
rootProjectNodeModules.resolve(name).canonicalFile.toPath()
class JavaFsLinker : FSLinker {
override fun link(linkFile: File, target: File) {
Files.createSymbolicLink(linkFile.toPath(), target.toPath())
}
}
}