Skip to content

Commit

Permalink
Add Python Script task (#7242)
Browse files Browse the repository at this point in the history
  • Loading branch information
brcrista committed May 18, 2018
1 parent ff0318c commit e108f3e
Show file tree
Hide file tree
Showing 12 changed files with 847 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"loc.friendlyName": "Python Script",
"loc.helpMarkDown": "",
"loc.description": "Run a Python script.",
"loc.instanceNameFormat": "Run Python script",
"loc.group.displayName.advanced": "Advanced",
"loc.input.label.scriptSource": "Script source",
"loc.input.help.scriptSource": "Whether the script is a file in the source tree or is written inline in this task.",
"loc.input.label.scriptPath": "Script path",
"loc.input.help.scriptPath": "Path of the script to execute. Must be a fully qualified path or relative to $(System.DefaultWorkingDirectory).",
"loc.input.label.script": "Script",
"loc.input.help.script": "The Python script to run",
"loc.input.label.arguments": "Arguments",
"loc.input.help.arguments": "Arguments passed to the script execution, available through `sys.argv`.",
"loc.input.label.pythonInterpreter": "Python interpreter",
"loc.input.help.pythonInterpreter": "Absolute path to the Python interpreter to use. If not specified, the task will use the interpreter in PATH.<br /> Run the [Use Python Version](https://go.microsoft.com/fwlink/?linkid=871498) task to add a version of Python to PATH.",
"loc.input.label.workingDirectory": "Working directory",
"loc.input.label.failOnStderr": "Fail on standard error",
"loc.input.help.failOnStderr": "If this is true, this task will fail if any text is written to the stderr stream.",
"loc.messages.NotAFile": "The given path was not to a file: '%s'.",
"loc.messages.ParameterRequired": "The `%s` parameter is required"
}
349 changes: 349 additions & 0 deletions Tasks/PythonScriptV0/ThirdPartyNotice.txt

Large diffs are not rendered by default.

Binary file added Tasks/PythonScriptV0/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions Tasks/PythonScriptV0/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as path from 'path';
import * as task from 'vsts-task-lib/task';
import { pythonScript } from './pythonscript';

(async () => {
try {
task.setResourcePath(path.join(__dirname, 'task.json'));
await pythonScript({
scriptSource: task.getInput('scriptSource'),
scriptPath: task.getPathInput('scriptPath'),
script: task.getInput('script'),
arguments: task.getInput('arguments'),
pythonInterpreter: task.getInput('pythonInterpreter'), // string instead of path: a path will default to the agent's sources directory
workingDirectory: task.getPathInput('workingDirectory'),
failOnStderr: task.getBoolInput('failOnStderr')
});
task.setResult(task.TaskResult.Succeeded, "");
} catch (error) {
task.error(error.message);
task.setResult(task.TaskResult.Failed, error.message);
}
})();
91 changes: 91 additions & 0 deletions Tasks/PythonScriptV0/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions Tasks/PythonScriptV0/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "python-script",
"version": "1.0.0",
"description": "Create and activate a Conda environment.",
"main": "pythonscript.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/microsoft/vsts-tasks.git"
},
"author": "Microsoft Corporation",
"license": "MIT",
"bugs": {
"url": "https://github.com/microsoft/vsts-tasks/issues"
},
"homepage": "https://github.com/microsoft/vsts-tasks#readme",
"dependencies": {
"@types/node": "^6.0.101",
"@types/q": "^1.5.0",
"@types/uuid": "^3.4.3",
"vsts-task-lib": "^2.4.0"
}
}
89 changes: 89 additions & 0 deletions Tasks/PythonScriptV0/pythonscript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as fs from 'fs';
import * as path from 'path';

import * as task from 'vsts-task-lib/task';
import * as toolRunner from 'vsts-task-lib/toolrunner';

import * as uuidV4 from 'uuid/v4';

interface TaskParameters {
scriptSource: string,
scriptPath?: string,
script?: string,
arguments?: string,
pythonInterpreter?: string,
workingDirectory?: string,
failOnStderr?: boolean
}

/**
* Check for a parameter at runtime.
* Useful for conditionally-visible, required parameters.
*/
function assertParameter<T>(value: T | undefined, propertyName: string): T {
if (!value) {
throw new Error(task.loc('ParameterRequired', propertyName));
}

return value!;
}

// TODO Enable with TypeScript 2.8 (ensures correct property name in the error message)
// function assertParameter<T extends keyof TaskParameters>(parameters: TaskParameters, propertyName: T): NonNullable<TaskParameters[T]> {
// const param = parameters[propertyName];
// if (!param) {
// throw new Error(task.loc('ParameterRequired', propertyName));
// }

// return param!;
// }

export async function pythonScript(parameters: Readonly<TaskParameters>): Promise<void> {
// Get the script to run
const scriptPath = await (async () => {
if (parameters.scriptSource.toLowerCase() === 'filepath') { // Run script file
const scriptPath = assertParameter(parameters.scriptPath, 'scriptPath');

if (!fs.statSync(scriptPath).isFile()) {
throw new Error(task.loc('NotAFile', scriptPath));
}
return scriptPath;
} else { // Run inline script
const script = assertParameter(parameters.script, 'script');

// Write the script to disk
task.assertAgent('2.115.0');
const tempDirectory = task.getVariable('agent.tempDirectory');
task.checkPath(tempDirectory, `${tempDirectory} (agent.tempDirectory)`);
const scriptPath = path.join(tempDirectory, `${uuidV4()}.py`);
await fs.writeFileSync(
scriptPath,
script,
{ encoding: 'utf8' });

return scriptPath;
}
})();

// Create the tool runner
const pythonPath = parameters.pythonInterpreter || task.which('python');
const python = task.tool(pythonPath).arg(scriptPath);

// Calling `line` with a falsy argument returns `undefined`, so can't chain this call
if (parameters.arguments) {
python.line(parameters.arguments);
}

// Run the script
// Use `any` to work around what I suspect are bugs with `IExecOptions`'s type annotations:
// - optional fields need to be typed as optional
// - `errStream` and `outStream` should be `NodeJs.WritableStream`, not `NodeJS.Writable`
await python.exec(<any>{
cwd: parameters.workingDirectory,
failOnStdErr: parameters.failOnStderr,
// Direct all output to stdout, otherwise the output may appear out-of-order since Node buffers its own stdout but not stderr
errStream: process.stdout,
outStream: process.stdout,
ignoreReturnCode: false
});
}
113 changes: 113 additions & 0 deletions Tasks/PythonScriptV0/task.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{
"id": "6392F95F-7E76-4A18-B3C7-7F078D2F7700",
"name": "PythonScript",
"friendlyName": "Python Script",
"description": "Run a Python script.",
"helpMarkDown": "",
"category": "Utility",
"visibility": [
"Build",
"Release"
],
"runsOn": [
"Agent",
"DeploymentGroup"
],
"author": "Microsoft Corporation",
"version": {
"Major": 0,
"Minor": 135,
"Patch": 0
},
"preview": true,
"demands": [],
"instanceNameFormat": "Run Python script",
"groups": [
{
"name": "advanced",
"displayName": "Advanced",
"isExpanded": false
}
],
"inputs": [
{
"name": "scriptSource",
"type": "radio",
"label": "Script source",
"required": true,
"defaultValue": "filePath",
"helpMarkDown": "Whether the script is a file in the source tree or is written inline in this task.",
"options": {
"filePath": "File path",
"inline": "Inline"
}
},
{
"name": "scriptPath",
"type": "filePath",
"label": "Script path",
"visibleRule": "scriptSource = filePath",
"required": true,
"defaultValue": "",
"helpMarkDown": "Path of the script to execute. Must be a fully qualified path or relative to $(System.DefaultWorkingDirectory)."
},
{
"name": "script",
"type": "multiLine",
"label": "Script",
"visibleRule": "scriptSource = inline",
"required": true,
"defaultValue": "",
"properties": {
"resizable": "true",
"rows": "10",
"maxLength": "5000"
},
"helpMarkDown": "The Python script to run"
},
{
"name": "arguments",
"type": "string",
"label": "Arguments",
"required": false,
"defaultValue": "",
"helpMarkDown": "Arguments passed to the script execution, available through `sys.argv`."
},
{
"name": "pythonInterpreter",
"type": "string",
"label": "Python interpreter",
"defaultValue": "",
"required": false,
"helpMarkDown": "Absolute path to the Python interpreter to use. If not specified, the task will use the interpreter in PATH.<br /> Run the [Use Python Version](https://go.microsoft.com/fwlink/?linkid=871498) task to add a version of Python to PATH.",
"groupName": "advanced"
},
{
"name": "workingDirectory",
"type": "filePath",
"label": "Working directory",
"defaultValue": "",
"required": false,
"groupName": "advanced"
},
{
"name": "failOnStderr",
"type": "boolean",
"label": "Fail on standard error",
"defaultValue": "false",
"required": false,
"helpMarkDown": "If this is true, this task will fail if any text is written to the stderr stream.",
"groupName": "advanced"
}
],
"execution": {
"Node": {
"target": "main.js",
"argumentFormat": ""
}
},
"messages": {
"NotAFile": "The given path was not to a file: '%s'.",
"ParameterRequired": "The `%s` parameter is required"
}
}
Loading

0 comments on commit e108f3e

Please sign in to comment.