Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix spaces on Windows in cmd and args #113

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions node/test/scripts/path with spaces/check-arg.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@echo off

rem Exit with code 0 only if there is exactly one argument
echo %1

set argCount=0
for %%x in (%*) do set /a argCount+=1
if %argCount%==1 exit /b 0
exit /b 1
10 changes: 10 additions & 0 deletions node/test/scripts/path with spaces/check-arg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/sh

echo $1

# Exit with code 0 only if there is exactly one argument
if test $# -eq 1; then
exit 0
else
exit 1
fi
52 changes: 51 additions & 1 deletion node/test/tasklib.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -1591,7 +1591,7 @@ describe('Test vsts-task-lib', function () {
assert.equal(node.args.length, 5, 'should have 5 args');
assert.equal(node.args.toString(), 'one,two,three,four,five', 'should be one,two,three,four,five');
done();
})
})
it('handles padded spaces', function (done) {
this.timeout(1000);

Expand Down Expand Up @@ -1671,6 +1671,56 @@ describe('Test vsts-task-lib', function () {
assert.equal(node.args.toString(), '--path,/bin/working folder1', 'should be --path /bin/working folder1');
done();
})
it('handles spaces in toolPath and arguments', function () {
this.timeout(1000);

var toolName = os.type().match(/^Win/) ? 'check-arg.cmd' : 'check-arg.sh';
var tool = tl.tool(path.join(__dirname, 'scripts', 'path with spaces', toolName));
tool.arg('arg with spaces');

var _testExecOptions: trm.IExecOptions = {
cwd: __dirname,
env: {},
silent: false,
failOnStdErr: false,
ignoreReturnCode: false,
outStream: _nullTestStream,
errStream: _nullTestStream
}

return tool.exec(_testExecOptions)
.then(function (code) {
assert.equal(code, 0, 'return code of script should be 0');
})
.fail(function (err) {
assert.fail('script failed to run: ' + err.message);
});
})
if (os.type().match(/^Win/)) {
it('handles spaces in toolPath for Windows exe', function () {
this.timeout(1000);

var toolDir = path.join(__dirname, 'scripts', 'path with spaces');
var toolPath = path.join(toolDir, 'cmd.exe');
tl.cp(tl.which('cmd'), toolDir);

var cmd = tl.tool(toolPath);
cmd.arg(['/c', 'echo hello']);
return cmd.exec({
cwd: __dirname,
env: {},
silent: false,
failOnStdErr: false,
ignoreReturnCode: false,
outStream: _nullTestStream,
errStream: _nullTestStream
}).then(function (code) {
assert.equal(code, 0, 'return code of command should be 0');
}).fail(function (err) {
assert.fail('command failed to run: ' + err.message);
});
})
}
});

describe('Codecoverage commands', function () {
Expand Down
75 changes: 44 additions & 31 deletions node/toolrunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var run = function(cmd, callback) {
console.log('running: ' + cmd);
var output = '';
try {

}
catch(err) {
console.log(err.message);
Expand All @@ -21,8 +21,8 @@ var run = function(cmd, callback) {

/**
* Interface for exec options
*
* @param cwd optional working directory. defaults to current
*
* @param cwd optional working directory. defaults to current
* @param env optional envvar dictionary. defaults to current processes env
* @param silent optional. defaults to false
* @param failOnStdErr optional. whether to fail if output to stderr. defaults to false
Expand All @@ -40,7 +40,7 @@ export interface IExecOptions {

/**
* Interface for exec results returned from synchronous exec functions
*
*
* @param stdout standard output
* @param stderr error output
* @param code return code
Expand All @@ -56,7 +56,7 @@ export interface IExecResult {
export class ToolRunner extends events.EventEmitter {
constructor(toolPath) {
super();

this.toolPath = toolPath;
this.args = [];
this.silent = false;
Expand Down Expand Up @@ -102,7 +102,7 @@ export class ToolRunner extends events.EventEmitter {
}
continue;
}

if (c === "\\" && inQuotes) {
escaped = true;
continue;
Expand All @@ -128,9 +128,9 @@ export class ToolRunner extends events.EventEmitter {

/**
* Add argument
* Append an argument or an array of arguments
* Append an argument or an array of arguments
* returns ToolRunner for chaining
*
*
* @param val string cmdline or array of strings
* @returns ToolRunner
*/
Expand All @@ -154,8 +154,8 @@ export class ToolRunner extends events.EventEmitter {
/**
* Append argument command line string
* e.g. '"arg one" two -z' would append args[]=['arg one', 'two', '-z']
* returns ToolRunner for chaining
*
* returns ToolRunner for chaining
*
* @param val string cmdline
* @returns ToolRunner
*/
Expand All @@ -166,9 +166,9 @@ export class ToolRunner extends events.EventEmitter {

this._debug(this.toolPath + ' arg: ' + val);
this.args = this.args.concat(this._argStringToArray(val));
return this;
return this;
}

/**
* Add argument(s) if a condition is met
* Wraps arg(). See arg for details
Expand All @@ -189,7 +189,7 @@ export class ToolRunner extends events.EventEmitter {
* Exec a tool.
* Output will be streamed to the live console.
* Returns promise with return code
*
*
* @param tool path to tool to exec
* @param options optional exec options. See IExecOptions
* @returns number
Expand Down Expand Up @@ -224,12 +224,25 @@ export class ToolRunner extends events.EventEmitter {
}

if (!ops.silent) {
ops.outStream.write('[command]' + cmdString + os.EOL);
ops.outStream.write('[command]' + cmdString + os.EOL);
}

var runSettings;
if (os.type().match(/^Win/)) {
runSettings = {
shell: this.toolPath.indexOf(' ') !== -1,
toolPath: this.toolPath.indexOf(' ') === -1 ? this.toolPath : '"' + this.toolPath + '"',
args: this.args.map((arg: string) => /^[^"].* .*[^"]$/.test(arg) ? '"' + arg + '"' : arg)
}
} else {
runSettings = {
shell: false,
toolPath: this.toolPath,
args: this.args
}
}
// TODO: filter process.env

var cp = child.spawn(this.toolPath, this.args, { cwd: ops.cwd, env: ops.env });
var cp = child.spawn(runSettings.toolPath, runSettings.args, { cwd: ops.cwd, env: ops.env, shell: runSettings.shell });

var processLineBuffer = (data: Buffer, strBuffer: string, onLine:(line: string) => void): void => {
try {
Expand All @@ -245,7 +258,7 @@ export class ToolRunner extends events.EventEmitter {
n = s.indexOf(os.EOL);
}

strBuffer = s;
strBuffer = s;
}
catch (err) {
// streaming lines to console is best effort. Don't fail a build.
Expand All @@ -259,11 +272,11 @@ export class ToolRunner extends events.EventEmitter {
this.emit('stdout', data);

if (!ops.silent) {
ops.outStream.write(data);
ops.outStream.write(data);
}

processLineBuffer(data, stdbuffer, (line: string) => {
this.emit('stdline', line);
this.emit('stdline', line);
});
});

Expand All @@ -278,8 +291,8 @@ export class ToolRunner extends events.EventEmitter {
}

processLineBuffer(data, errbuffer, (line: string) => {
this.emit('errline', line);
});
this.emit('errline', line);
});
});

cp.on('error', (err) => {
Expand All @@ -292,33 +305,33 @@ export class ToolRunner extends events.EventEmitter {
if (stdbuffer.length > 0) {
this.emit('stdline', stdbuffer);
}

if (errbuffer.length > 0) {
this.emit('errline', errbuffer);
}

if (code != 0 && !ops.ignoreReturnCode) {
success = false;
}

this._debug('success:' + success);
if (success) {
defer.resolve(code);
}
else {
defer.reject(new Error(this.toolPath + ' failed with return code: ' + code));
}
}
});

return <Q.Promise<number>>defer.promise;
}

/**
* Exec a tool synchronously.
* Exec a tool synchronously.
* Output will be *not* be streamed to the live console. It will be returned after execution is complete.
* Appropriate for short running tools
* Appropriate for short running tools
* Returns IExecResult with output and return code
*
*
* @param tool path to tool to exec
* @param options optionalexec options. See IExecOptions
* @returns IExecResult
Expand Down Expand Up @@ -353,11 +366,11 @@ export class ToolRunner extends events.EventEmitter {
}

if (!ops.silent) {
ops.outStream.write('[command]' + cmdString + os.EOL);
ops.outStream.write('[command]' + cmdString + os.EOL);
}

var r = child.spawnSync(this.toolPath, this.args, { cwd: ops.cwd, env: ops.env });

if (r.stdout && r.stdout.length > 0) {
ops.outStream.write(r.stdout);
}
Expand All @@ -370,5 +383,5 @@ export class ToolRunner extends events.EventEmitter {
res.stdout = (r.stdout) ? r.stdout.toString() : null;
res.stderr = (r.stderr) ? r.stderr.toString() : null;
return res;
}
}
}