shells/cmd.go (210 lines of code) (raw):

package shells import ( "bufio" "bytes" "fmt" "io" "path" "path/filepath" "runtime" "strings" "gitlab.com/gitlab-org/gitlab-ci-multi-runner/common" "gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers" ) type CmdShell struct { AbstractShell } type CmdWriter struct { bytes.Buffer TemporaryPath string indent int } func batchQuote(text string) string { return "\"" + batchEscapeInsideQuotedString(text) + "\"" } func batchEscapeInsideQuotedString(text string) string { // taken from: http://www.robvanderwoude.com/escapechars.php text = strings.Replace(text, "^", "^^", -1) text = strings.Replace(text, "!", "^^!", -1) text = strings.Replace(text, "&", "^&", -1) text = strings.Replace(text, "<", "^<", -1) text = strings.Replace(text, ">", "^>", -1) text = strings.Replace(text, "|", "^|", -1) text = strings.Replace(text, "\r", "", -1) text = strings.Replace(text, "\n", "!nl!", -1) return text } func batchEscapeVariable(text string) string { text = strings.Replace(text, "%", "%%", -1) text = batchEscape(text) return text } // If not inside a quoted string (e.g., echo text), escape more things func batchEscape(text string) string { text = batchEscapeInsideQuotedString(text) text = strings.Replace(text, "(", "^(", -1) text = strings.Replace(text, ")", "^)", -1) return text } func (b *CmdShell) GetName() string { return "cmd" } func (b *CmdWriter) GetTemporaryPath() string { return b.TemporaryPath } func (b *CmdWriter) Line(text string) { b.WriteString(strings.Repeat(" ", b.indent) + text + "\r\n") } func (b *CmdWriter) CheckForErrors() { b.checkErrorLevel() } func (b *CmdWriter) Indent() { b.indent++ } func (b *CmdWriter) Unindent() { b.indent-- } func (b *CmdWriter) checkErrorLevel() { b.Line("IF %errorlevel% NEQ 0 exit /b %errorlevel%") b.Line("") } func (b *CmdWriter) Command(command string, arguments ...string) { b.Line(b.buildCommand(command, arguments...)) b.checkErrorLevel() } func (b *CmdWriter) buildCommand(command string, arguments ...string) string { list := []string{ batchQuote(command), } for _, argument := range arguments { list = append(list, batchQuote(argument)) } return strings.Join(list, " ") } func (b *CmdWriter) Variable(variable common.JobVariable) { if variable.File { variableFile := b.Absolute(path.Join(b.TemporaryPath, variable.Key)) variableFile = helpers.ToBackslash(variableFile) b.Line(fmt.Sprintf("md %q 2>NUL 1>NUL", batchEscape(helpers.ToBackslash(b.TemporaryPath)))) b.Line(fmt.Sprintf("echo %s > %s", batchEscapeVariable(variable.Value), batchEscape(variableFile))) b.Line("SET " + batchEscapeVariable(variable.Key) + "=" + batchEscape(variableFile)) } else { b.Line("SET " + batchEscapeVariable(variable.Key) + "=" + batchEscapeVariable(variable.Value)) } } func (b *CmdWriter) IfDirectory(path string) { b.Line("IF EXIST " + batchQuote(helpers.ToBackslash(path)) + " (") b.Indent() } func (b *CmdWriter) IfFile(path string) { b.Line("IF EXIST " + batchQuote(helpers.ToBackslash(path)) + " (") b.Indent() } func (b *CmdWriter) IfCmd(cmd string, arguments ...string) { cmdline := b.buildCommand(cmd, arguments...) b.Line(fmt.Sprintf("%s 2>NUL 1>NUL", cmdline)) b.Line("IF %errorlevel% EQU 0 (") b.Indent() } func (b *CmdWriter) IfCmdWithOutput(cmd string, arguments ...string) { cmdline := b.buildCommand(cmd, arguments...) b.Line(fmt.Sprintf("%s", cmdline)) b.Line("IF %errorlevel% EQU 0 (") b.Indent() } func (b *CmdWriter) Else() { b.Unindent() b.Line(") ELSE (") b.Indent() } func (b *CmdWriter) EndIf() { b.Unindent() b.Line(")") } func (b *CmdWriter) Cd(path string) { b.Line("cd /D " + batchQuote(helpers.ToBackslash(path))) b.checkErrorLevel() } func (b *CmdWriter) MkDir(path string) { args := batchQuote(helpers.ToBackslash(path)) + " 2>NUL 1>NUL" b.Line("dir " + args + " || md " + args) } func (b *CmdWriter) MkTmpDir(name string) string { path := helpers.ToBackslash(path.Join(b.TemporaryPath, name)) b.MkDir(path) return path } func (b *CmdWriter) RmDir(path string) { b.Line("rd /s /q " + batchQuote(helpers.ToBackslash(path)) + " 2>NUL 1>NUL") } func (b *CmdWriter) RmFile(path string) { b.Line("rd /s /q " + batchQuote(helpers.ToBackslash(path)) + " 2>NUL 1>NUL") } func (b *CmdWriter) Print(format string, arguments ...interface{}) { coloredText := helpers.ANSI_RESET + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET b.Line("echo " + batchEscapeVariable(coloredText)) } func (b *CmdWriter) Notice(format string, arguments ...interface{}) { coloredText := helpers.ANSI_BOLD_GREEN + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET b.Line("echo " + batchEscapeVariable(coloredText)) } func (b *CmdWriter) Warning(format string, arguments ...interface{}) { coloredText := helpers.ANSI_YELLOW + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET b.Line("echo " + batchEscapeVariable(coloredText)) } func (b *CmdWriter) Error(format string, arguments ...interface{}) { coloredText := helpers.ANSI_BOLD_RED + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET b.Line("echo " + batchEscapeVariable(coloredText)) } func (b *CmdWriter) EmptyLine() { b.Line("echo.") } func (b *CmdWriter) Absolute(dir string) string { if filepath.IsAbs(dir) { return dir } return filepath.Join("%CD%", dir) } func (b *CmdWriter) Finish(trace bool) string { var buffer bytes.Buffer w := bufio.NewWriter(&buffer) if trace { io.WriteString(w, "@echo on\r\n") } else { io.WriteString(w, "@echo off\r\n") } io.WriteString(w, "setlocal enableextensions\r\n") io.WriteString(w, "setlocal enableDelayedExpansion\r\n") io.WriteString(w, "set nl=^\r\n\r\n\r\n") io.WriteString(w, b.String()) w.Flush() return buffer.String() } func (b *CmdShell) GetConfiguration(info common.ShellScriptInfo) (script *common.ShellConfiguration, err error) { script = &common.ShellConfiguration{ Command: "cmd", Arguments: []string{"/C"}, PassFile: true, Extension: "cmd", } return } func (b *CmdShell) GenerateScript(buildStage common.BuildStage, info common.ShellScriptInfo) (script string, err error) { w := &CmdWriter{ TemporaryPath: info.Build.FullProjectDir() + ".tmp", } if buildStage == common.BuildStagePrepare { if len(info.Build.Hostname) != 0 { w.Line("echo Running on %COMPUTERNAME% via " + batchEscape(info.Build.Hostname) + "...") } else { w.Line("echo Running on %COMPUTERNAME%...") } } err = b.writeScript(w, buildStage, info) script = w.Finish(info.Build.IsDebugTraceEnabled()) return } func (b *CmdShell) IsDefault() bool { return runtime.GOOS == "windows" } func init() { common.RegisterShell(&CmdShell{}) }