Gradle, NPM, windows: creating symlink without administrator privilege
#KT-31694 Fixed
This commit is contained in:
+61
-5
@@ -5,15 +5,19 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.gradle.targets.js.npm
|
package org.jetbrains.kotlin.gradle.targets.js.npm
|
||||||
|
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
|
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
|
import java.nio.file.Files
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles simple case, when there are no npm depenencies required, but some tasks steel needs
|
* Handles simple case, when there are no npm depenencies required, but some tasks steel needs
|
||||||
* node_modules with symlinked packages and packages_imported
|
* 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)
|
private val rootProjectNodeModules = rootProject.nodeJs.root.rootPackageDir.resolve(NpmProject.NODE_MODULES)
|
||||||
|
|
||||||
fun link(projects: Collection<NpmProjectPackage>) {
|
fun link(projects: Collection<NpmProjectPackage>) {
|
||||||
@@ -29,19 +33,71 @@ class NpmSimpleLinker(rootProject: Project) {
|
|||||||
|
|
||||||
rootProjectNodeModules.mkdirs()
|
rootProjectNodeModules.mkdirs()
|
||||||
|
|
||||||
|
val fsLinker = createFSLinker()
|
||||||
|
|
||||||
// packages
|
// packages
|
||||||
projects.forEach {
|
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
|
// packages_imported
|
||||||
projects.flatMapTo(mutableSetOf()) {
|
projects.flatMapTo(mutableSetOf()) {
|
||||||
it.gradleDependencies.externalModules.map { gradleNodeModule -> gradleNodeModule }
|
it.gradleDependencies.externalModules.map { gradleNodeModule -> gradleNodeModule }
|
||||||
}.forEach {
|
}.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) =
|
class JavaFsLinker : FSLinker {
|
||||||
rootProjectNodeModules.resolve(name).canonicalFile.toPath()
|
override fun link(linkFile: File, target: File) {
|
||||||
|
Files.createSymbolicLink(linkFile.toPath(), target.toPath())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user