diff --git a/build.gradle b/build.gradle index f587cf81d3..b06d4c0b21 100644 --- a/build.gradle +++ b/build.gradle @@ -71,6 +71,7 @@ buildscript { ext.class_graph_version = '4.2.12' ext.jcabi_manifests_version = '1.1' ext.picocli_version = '3.8.0' + ext.commons_io_version = '2.6' // Name of the IntelliJ SDK created for the deterministic Java rt.jar. // ext.deterministic_idea_sdk = '1.8 (Deterministic)' diff --git a/tools/cliutils/build.gradle b/tools/cliutils/build.gradle index 0f341432b7..d505751347 100644 --- a/tools/cliutils/build.gradle +++ b/tools/cliutils/build.gradle @@ -9,6 +9,7 @@ dependencies { compile project(":core") compile "info.picocli:picocli:$picocli_version" + compile "commons-io:commons-io:$commons_io_version" compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" compile "org.slf4j:slf4j-api:$slf4j_version" compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" diff --git a/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt b/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt index c2c6586145..43cb881bfc 100644 --- a/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt +++ b/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt @@ -184,6 +184,10 @@ abstract class CordaCliWrapper(alias: String, description: String) : CliWrapperB } fun printHelp() = cmd.usage(System.out) + + fun printlnErr(message: String) = System.err.println(message) + + fun printlnWarn(message: String) = System.err.println(message) } /** diff --git a/tools/cliutils/src/main/kotlin/net/corda/cliutils/InstallShellExtensionsParser.kt b/tools/cliutils/src/main/kotlin/net/corda/cliutils/InstallShellExtensionsParser.kt index a9be0cbae0..17f5bbe876 100644 --- a/tools/cliutils/src/main/kotlin/net/corda/cliutils/InstallShellExtensionsParser.kt +++ b/tools/cliutils/src/main/kotlin/net/corda/cliutils/InstallShellExtensionsParser.kt @@ -1,6 +1,8 @@ package net.corda.cliutils import net.corda.core.internal.* +import org.apache.commons.io.IOUtils +import org.apache.commons.lang.SystemUtils import picocli.CommandLine import picocli.CommandLine.Command import java.nio.file.Path @@ -9,6 +11,10 @@ import java.nio.file.StandardCopyOption import java.util.* private class ShellExtensionsGenerator(val parent: CordaCliWrapper) { + private companion object { + private const val minSupportedBashVersion = 4 + } + private class SettingsFile(val filePath: Path) { private val lines: MutableList by lazy { getFileLines() } var fileModified: Boolean = false @@ -80,10 +86,22 @@ private class ShellExtensionsGenerator(val parent: CordaCliWrapper) { autoCompleteFile.writeText(builder.toString()) } - fun installShellExtensions() { + fun installShellExtensions(): Int { // Get jar location and generate alias command val command = "alias ${parent.alias}='java -jar \"${jarLocation.toStringWithDeWindowsfication()}\"'" - generateAutoCompleteFile(parent.alias) + var generateAutoCompleteFile = true + if (SystemUtils.IS_OS_UNIX && installedShell() == ShellType.BASH) { + val semanticParts = declaredBashVersion().split(".") + semanticParts.firstOrNull()?.toIntOrNull()?.let { major -> + if (major < minSupportedBashVersion) { + parent.printlnWarn("Cannot install shell extension for bash major version earlier than $minSupportedBashVersion. Please upgrade your bash version. Aliases should still work.") + generateAutoCompleteFile = false + } + } + } + if (generateAutoCompleteFile) { + generateAutoCompleteFile(parent.alias) + } // Get bash settings file val bashSettingsFile = SettingsFile(userHome / ".bashrc") @@ -101,9 +119,34 @@ private class ShellExtensionsGenerator(val parent: CordaCliWrapper) { zshSettingsFile.addIfNotExists(completionFileCommand) zshSettingsFile.updateAndBackupIfNecessary() - println("Installation complete, ${parent.alias} is available in bash with autocompletion. ") + if (generateAutoCompleteFile) { + println("Installation complete, ${parent.alias} is available in bash with autocompletion.") + } else { + println("Installation complete, ${parent.alias} is available in bash, but autocompletion was not installed because of an old version of bash.") + } println("Type `${parent.alias} ` from the commandline.") println("Restart bash for this to take effect, or run `. ~/.bashrc` in bash or `. ~/.zshrc` in zsh to re-initialise your shell now") + return ExitCodes.SUCCESS + } + + private fun declaredBashVersion(): String = execCommand("bash -c 'echo \$BASH_VERSION'") + + private fun installedShell(): ShellType { + val path = execCommand("bash -c 'echo \$SHELL'") + return when { + path.endsWith("/zsh") -> ShellType.ZSH + path.endsWith("/bash") -> ShellType.BASH + else -> ShellType.OTHER + } + } + + private enum class ShellType { + ZSH, BASH, OTHER + } + + private fun execCommand(command: String): String { + val process = ProcessBuilder(command) + return IOUtils.toString(process.start().inputStream, Charsets.UTF_8) } fun checkForAutoCompleteUpdate() { @@ -127,8 +170,7 @@ private class ShellExtensionsGenerator(val parent: CordaCliWrapper) { class InstallShellExtensionsParser(private val cliWrapper: CordaCliWrapper) : CliWrapperBase("install-shell-extensions", "Install alias and autocompletion for bash and zsh") { private val generator = ShellExtensionsGenerator(cliWrapper) override fun runProgram(): Int { - generator.installShellExtensions() - return ExitCodes.SUCCESS + return generator.installShellExtensions() } fun updateShellExtensions() = generator.checkForAutoCompleteUpdate()