github.com/alloyci/alloy-runner@v1.0.1-0.20180222164613-925503ccafd6/shells/cmd.go (about)

     1  package shells
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"path"
     9  	"path/filepath"
    10  	"runtime"
    11  	"strings"
    12  
    13  	"gitlab.com/gitlab-org/gitlab-runner/common"
    14  	"gitlab.com/gitlab-org/gitlab-runner/helpers"
    15  )
    16  
    17  type CmdShell struct {
    18  	AbstractShell
    19  }
    20  
    21  type CmdWriter struct {
    22  	bytes.Buffer
    23  	TemporaryPath string
    24  	indent        int
    25  }
    26  
    27  func batchQuote(text string) string {
    28  	return "\"" + batchEscapeInsideQuotedString(text) + "\""
    29  }
    30  
    31  func batchEscapeInsideQuotedString(text string) string {
    32  	// taken from: http://www.robvanderwoude.com/escapechars.php
    33  	text = strings.Replace(text, "^", "^^", -1)
    34  	text = strings.Replace(text, "!", "^^!", -1)
    35  	text = strings.Replace(text, "&", "^&", -1)
    36  	text = strings.Replace(text, "<", "^<", -1)
    37  	text = strings.Replace(text, ">", "^>", -1)
    38  	text = strings.Replace(text, "|", "^|", -1)
    39  	text = strings.Replace(text, "\r", "", -1)
    40  	text = strings.Replace(text, "\n", "!nl!", -1)
    41  	return text
    42  }
    43  
    44  func batchEscapeVariable(text string) string {
    45  	text = strings.Replace(text, "%", "%%", -1)
    46  	text = batchEscape(text)
    47  	return text
    48  }
    49  
    50  // If not inside a quoted string (e.g., echo text), escape more things
    51  func batchEscape(text string) string {
    52  	text = batchEscapeInsideQuotedString(text)
    53  	text = strings.Replace(text, "(", "^(", -1)
    54  	text = strings.Replace(text, ")", "^)", -1)
    55  	return text
    56  }
    57  
    58  func (b *CmdShell) GetName() string {
    59  	return "cmd"
    60  }
    61  
    62  func (b *CmdWriter) GetTemporaryPath() string {
    63  	return b.TemporaryPath
    64  }
    65  
    66  func (b *CmdWriter) Line(text string) {
    67  	b.WriteString(strings.Repeat("  ", b.indent) + text + "\r\n")
    68  }
    69  
    70  func (b *CmdWriter) CheckForErrors() {
    71  	b.checkErrorLevel()
    72  }
    73  
    74  func (b *CmdWriter) Indent() {
    75  	b.indent++
    76  }
    77  
    78  func (b *CmdWriter) Unindent() {
    79  	b.indent--
    80  }
    81  
    82  func (b *CmdWriter) checkErrorLevel() {
    83  	b.Line("IF %errorlevel% NEQ 0 exit /b %errorlevel%")
    84  	b.Line("")
    85  }
    86  
    87  func (b *CmdWriter) Command(command string, arguments ...string) {
    88  	b.Line(b.buildCommand(command, arguments...))
    89  	b.checkErrorLevel()
    90  }
    91  
    92  func (b *CmdWriter) buildCommand(command string, arguments ...string) string {
    93  	list := []string{
    94  		batchQuote(command),
    95  	}
    96  
    97  	for _, argument := range arguments {
    98  		list = append(list, batchQuote(argument))
    99  	}
   100  
   101  	return strings.Join(list, " ")
   102  }
   103  
   104  func (b *CmdWriter) TmpFile(name string) string {
   105  	filePath := b.Absolute(path.Join(b.TemporaryPath, name))
   106  	return helpers.ToBackslash(filePath)
   107  }
   108  
   109  func (b *CmdWriter) Variable(variable common.JobVariable) {
   110  	if variable.File {
   111  		variableFile := b.TmpFile(variable.Key)
   112  		b.Line(fmt.Sprintf("md %q 2>NUL 1>NUL", batchEscape(helpers.ToBackslash(b.TemporaryPath))))
   113  		b.Line(fmt.Sprintf("echo %s > %s", batchEscapeVariable(variable.Value), batchEscape(variableFile)))
   114  		b.Line("SET " + batchEscapeVariable(variable.Key) + "=" + batchEscape(variableFile))
   115  	} else {
   116  		b.Line("SET " + batchEscapeVariable(variable.Key) + "=" + batchEscapeVariable(variable.Value))
   117  	}
   118  }
   119  
   120  func (b *CmdWriter) IfDirectory(path string) {
   121  	b.Line("IF EXIST " + batchQuote(helpers.ToBackslash(path)) + " (")
   122  	b.Indent()
   123  }
   124  
   125  func (b *CmdWriter) IfFile(path string) {
   126  	b.Line("IF EXIST " + batchQuote(helpers.ToBackslash(path)) + " (")
   127  	b.Indent()
   128  }
   129  
   130  func (b *CmdWriter) IfCmd(cmd string, arguments ...string) {
   131  	cmdline := b.buildCommand(cmd, arguments...)
   132  	b.Line(fmt.Sprintf("%s 2>NUL 1>NUL", cmdline))
   133  	b.Line("IF %errorlevel% EQU 0 (")
   134  	b.Indent()
   135  }
   136  
   137  func (b *CmdWriter) IfCmdWithOutput(cmd string, arguments ...string) {
   138  	cmdline := b.buildCommand(cmd, arguments...)
   139  	b.Line(fmt.Sprintf("%s", cmdline))
   140  	b.Line("IF %errorlevel% EQU 0 (")
   141  	b.Indent()
   142  }
   143  
   144  func (b *CmdWriter) Else() {
   145  	b.Unindent()
   146  	b.Line(") ELSE (")
   147  	b.Indent()
   148  }
   149  
   150  func (b *CmdWriter) EndIf() {
   151  	b.Unindent()
   152  	b.Line(")")
   153  }
   154  
   155  func (b *CmdWriter) Cd(path string) {
   156  	b.Line("cd /D " + batchQuote(helpers.ToBackslash(path)))
   157  	b.checkErrorLevel()
   158  }
   159  
   160  func (b *CmdWriter) MkDir(path string) {
   161  	args := batchQuote(helpers.ToBackslash(path)) + " 2>NUL 1>NUL"
   162  	b.Line("dir " + args + " || md " + args)
   163  }
   164  
   165  func (b *CmdWriter) MkTmpDir(name string) string {
   166  	path := helpers.ToBackslash(path.Join(b.TemporaryPath, name))
   167  	b.MkDir(path)
   168  
   169  	return path
   170  }
   171  
   172  func (b *CmdWriter) RmDir(path string) {
   173  	b.Line("rd /s /q " + batchQuote(helpers.ToBackslash(path)) + " 2>NUL 1>NUL")
   174  }
   175  
   176  func (b *CmdWriter) RmFile(path string) {
   177  	b.Line("rd /s /q " + batchQuote(helpers.ToBackslash(path)) + " 2>NUL 1>NUL")
   178  }
   179  
   180  func (b *CmdWriter) Print(format string, arguments ...interface{}) {
   181  	coloredText := helpers.ANSI_RESET + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET
   182  	b.Line("echo " + batchEscapeVariable(coloredText))
   183  }
   184  
   185  func (b *CmdWriter) Notice(format string, arguments ...interface{}) {
   186  	coloredText := helpers.ANSI_BOLD_GREEN + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET
   187  	b.Line("echo " + batchEscapeVariable(coloredText))
   188  }
   189  
   190  func (b *CmdWriter) Warning(format string, arguments ...interface{}) {
   191  	coloredText := helpers.ANSI_YELLOW + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET
   192  	b.Line("echo " + batchEscapeVariable(coloredText))
   193  }
   194  
   195  func (b *CmdWriter) Error(format string, arguments ...interface{}) {
   196  	coloredText := helpers.ANSI_BOLD_RED + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET
   197  	b.Line("echo " + batchEscapeVariable(coloredText))
   198  }
   199  
   200  func (b *CmdWriter) EmptyLine() {
   201  	b.Line("echo.")
   202  }
   203  
   204  func (b *CmdWriter) Absolute(dir string) string {
   205  	if filepath.IsAbs(dir) {
   206  		return dir
   207  	}
   208  	return filepath.Join("%CD%", dir)
   209  }
   210  
   211  func (b *CmdWriter) Finish(trace bool) string {
   212  	var buffer bytes.Buffer
   213  	w := bufio.NewWriter(&buffer)
   214  
   215  	if trace {
   216  		io.WriteString(w, "@echo on\r\n")
   217  	} else {
   218  		io.WriteString(w, "@echo off\r\n")
   219  	}
   220  
   221  	io.WriteString(w, "setlocal enableextensions\r\n")
   222  	io.WriteString(w, "setlocal enableDelayedExpansion\r\n")
   223  	io.WriteString(w, "set nl=^\r\n\r\n\r\n")
   224  
   225  	io.WriteString(w, b.String())
   226  	w.Flush()
   227  	return buffer.String()
   228  }
   229  
   230  func (b *CmdShell) GetConfiguration(info common.ShellScriptInfo) (script *common.ShellConfiguration, err error) {
   231  	script = &common.ShellConfiguration{
   232  		Command:   "cmd",
   233  		Arguments: []string{"/C"},
   234  		PassFile:  true,
   235  		Extension: "cmd",
   236  	}
   237  	return
   238  }
   239  
   240  func (b *CmdShell) GenerateScript(buildStage common.BuildStage, info common.ShellScriptInfo) (script string, err error) {
   241  	w := &CmdWriter{
   242  		TemporaryPath: info.Build.FullProjectDir() + ".tmp",
   243  	}
   244  
   245  	if buildStage == common.BuildStagePrepare {
   246  		if len(info.Build.Hostname) != 0 {
   247  			w.Line("echo Running on %COMPUTERNAME% via " + batchEscape(info.Build.Hostname) + "...")
   248  		} else {
   249  			w.Line("echo Running on %COMPUTERNAME%...")
   250  		}
   251  	}
   252  
   253  	err = b.writeScript(w, buildStage, info)
   254  	script = w.Finish(info.Build.IsDebugTraceEnabled())
   255  	return
   256  }
   257  
   258  func (b *CmdShell) IsDefault() bool {
   259  	return runtime.GOOS == "windows"
   260  }
   261  
   262  func init() {
   263  	common.RegisterShell(&CmdShell{})
   264  }