Skip to content

Commit d69ace1

Browse files
authored
Hotfix Use Python Version on m133 (#7149)
1 parent a93593a commit d69ace1

File tree

6 files changed

+127
-22
lines changed

6 files changed

+127
-22
lines changed

Tasks/UsePythonVersion/Strings/resources.resjson/en-US/resources.resjson

+3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
"loc.helpMarkDown": "[More Information](https://go.microsoft.com/fwlink/?linkid=871498)",
44
"loc.description": "Retrieves the specified version of Python from the tool cache. Optionally add it to PATH.",
55
"loc.instanceNameFormat": "Use Python $(versionSpec)",
6+
"loc.group.displayName.advanced": "Advanced",
67
"loc.input.label.versionSpec": "Version spec",
78
"loc.input.help.versionSpec": "Version range or exact version of a Python version to use.",
89
"loc.input.label.addToPath": "Add to PATH",
910
"loc.input.help.addToPath": "Whether to prepend the retrieved Python version to the PATH environment variable to make it available in subsequent tasks or scripts without using the output variable.",
11+
"loc.input.label.architecture": "Architecture",
12+
"loc.input.help.architecture": "The target architecture (x86, x64) of the Python interpreter.",
1013
"loc.messages.ListAvailableVersions": "Available versions:",
1114
"loc.messages.PlatformNotRecognized": "Platform not recognized",
1215
"loc.messages.PrependPath": "Prepending PATH environment variable with directory: %s",

Tasks/UsePythonVersion/Tests/L0.ts

+50-9
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,16 @@ describe('UsePythonVersion L0 Suite', function () {
8282
};
8383
mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, mockBuildVariables));
8484

85-
const toolPath = path.join('/', 'Python', '3.6.4');
85+
const toolPath = path.join('/', 'Python', '3.6.4', 'x64');
8686
mockery.registerMock('vsts-task-tool-lib/tool', {
8787
findLocalTool: () => toolPath
8888
});
8989

9090
const uut = reload();
9191
const parameters = {
9292
versionSpec: '3.6',
93-
addToPath: false
93+
addToPath: false,
94+
architecture: 'x64'
9495
};
9596

9697
assert.strictEqual(buildVariables['pythonLocation'], undefined);
@@ -103,13 +104,14 @@ describe('UsePythonVersion L0 Suite', function () {
103104
mockery.registerMock('vsts-task-lib/task', mockTask);
104105
mockery.registerMock('vsts-task-tool-lib/tool', {
105106
findLocalTool: () => null,
106-
findLocalToolVersions: () => ['2.7.13']
107+
findLocalToolVersions: () => ['2.6.0', '2.7.13']
107108
});
108109

109110
const uut = reload();
110111
const parameters = {
111112
versionSpec: '3.x',
112-
addToPath: false
113+
addToPath: false,
114+
architecture: 'x64'
113115
};
114116

115117
try {
@@ -119,14 +121,52 @@ describe('UsePythonVersion L0 Suite', function () {
119121
const expectedMessage = [
120122
'loc_mock_VersionNotFound 3.x',
121123
'loc_mock_ListAvailableVersions',
122-
'2.7.13'
124+
'2.6.0 (x86)',
125+
'2.7.13 (x86)',
126+
'2.6.0 (x64)',
127+
'2.7.13 (x64)'
123128
].join(EOL);
124129

125130
assert.strictEqual(e.message, expectedMessage);
126131
done();
127132
}
128133
});
129134

135+
it('selects architecture passed as input', async function () {
136+
let buildVariables: { [key: string]: string } = {};
137+
const mockBuildVariables = {
138+
setVariable: (variable: string, value: string) => {
139+
buildVariables[variable] = value;
140+
},
141+
getVariable: (variable: string) => buildVariables[variable]
142+
};
143+
mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, mockBuildVariables));
144+
145+
const x86ToolPath = path.join('/', 'Python', '3.6.4', 'x86');
146+
const x64ToolPath = path.join('/', 'Python', '3.6.4', 'x64');
147+
mockery.registerMock('vsts-task-tool-lib/tool', {
148+
findLocalTool: (toolName: string, versionSpec: string, arch?: string) => {
149+
if (arch === 'x86') {
150+
return x86ToolPath;
151+
} else {
152+
return x64ToolPath;
153+
}
154+
}
155+
});
156+
157+
const uut = reload();
158+
const parameters = {
159+
versionSpec: '3.6',
160+
addToPath: false,
161+
architecture: 'x86'
162+
};
163+
164+
assert.strictEqual(buildVariables['pythonLocation'], undefined);
165+
166+
await uut.usePythonVersion(parameters, Platform.Linux);
167+
assert.strictEqual(buildVariables['pythonLocation'], x86ToolPath);
168+
});
169+
130170
it('sets PATH correctly on Linux', async function () {
131171
mockery.registerMock('vsts-task-lib/task', mockTask);
132172

@@ -145,12 +185,12 @@ describe('UsePythonVersion L0 Suite', function () {
145185
const uut = reload();
146186
const parameters = {
147187
versionSpec: '3.6',
148-
outputVariable: 'Python',
149-
addToPath: true
188+
addToPath: true,
189+
architecture: 'x64'
150190
};
151191

152192
await uut.usePythonVersion(parameters, Platform.Linux);
153-
assert.strictEqual(`${toolPath}:`, mockPath);
193+
assert.strictEqual(`${path.join(toolPath, 'bin')}:${toolPath}:`, mockPath);
154194
});
155195

156196
it('sets PATH correctly on Windows', async function () {
@@ -173,7 +213,8 @@ describe('UsePythonVersion L0 Suite', function () {
173213
const uut = reload();
174214
const parameters = {
175215
versionSpec: '3.6',
176-
addToPath: true
216+
addToPath: true,
217+
architecture: 'x64'
177218
};
178219

179220
await uut.usePythonVersion(parameters, Platform.Windows);

Tasks/UsePythonVersion/main.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { usePythonVersion } from './usepythonversion';
88
task.setResourcePath(path.join(__dirname, 'task.json'));
99
await usePythonVersion({
1010
versionSpec: task.getInput('versionSpec', true),
11-
addToPath: task.getBoolInput('addToPath', true)
11+
addToPath: task.getBoolInput('addToPath', true),
12+
architecture: task.getInput('architecture', true)
1213
},
1314
getPlatform());
1415
task.setResult(task.TaskResult.Succeeded, "");

Tasks/UsePythonVersion/task.json

+21-1
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,19 @@
1212
"author": "Microsoft Corporation",
1313
"version": {
1414
"Major": 0,
15-
"Minor": 133,
15+
"Minor": 134,
1616
"Patch": 2
1717
},
1818
"preview": true,
1919
"demands": [],
2020
"instanceNameFormat": "Use Python $(versionSpec)",
21+
"groups": [
22+
{
23+
"name": "advanced",
24+
"displayName": "Advanced",
25+
"isExpanded": false
26+
}
27+
],
2128
"inputs": [
2229
{
2330
"name": "versionSpec",
@@ -34,6 +41,19 @@
3441
"required": true,
3542
"defaultValue": "true",
3643
"helpMarkDown": "Whether to prepend the retrieved Python version to the PATH environment variable to make it available in subsequent tasks or scripts without using the output variable."
44+
},
45+
{
46+
"name": "architecture",
47+
"type": "pickList",
48+
"label": "Architecture",
49+
"defaultValue": "x64",
50+
"required": true,
51+
"helpMarkDown": "The target architecture (x86, x64) of the Python interpreter.",
52+
"groupName": "advanced",
53+
"options": {
54+
"x86": "x86",
55+
"x64": "x64"
56+
}
3757
}
3858
],
3959
"outputVariables": [

Tasks/UsePythonVersion/task.loc.json

+21-1
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,19 @@
1212
"author": "Microsoft Corporation",
1313
"version": {
1414
"Major": 0,
15-
"Minor": 133,
15+
"Minor": 134,
1616
"Patch": 2
1717
},
1818
"preview": true,
1919
"demands": [],
2020
"instanceNameFormat": "ms-resource:loc.instanceNameFormat",
21+
"groups": [
22+
{
23+
"name": "advanced",
24+
"displayName": "ms-resource:loc.group.displayName.advanced",
25+
"isExpanded": false
26+
}
27+
],
2128
"inputs": [
2229
{
2330
"name": "versionSpec",
@@ -34,6 +41,19 @@
3441
"required": true,
3542
"defaultValue": "true",
3643
"helpMarkDown": "ms-resource:loc.input.help.addToPath"
44+
},
45+
{
46+
"name": "architecture",
47+
"type": "pickList",
48+
"label": "ms-resource:loc.input.label.architecture",
49+
"defaultValue": "x64",
50+
"required": true,
51+
"helpMarkDown": "ms-resource:loc.input.help.architecture",
52+
"groupName": "advanced",
53+
"options": {
54+
"x86": "x86",
55+
"x64": "x64"
56+
}
3757
}
3858
],
3959
"outputVariables": [

Tasks/UsePythonVersion/usepythonversion.ts

+30-10
Original file line numberDiff line numberDiff line change
@@ -11,48 +11,61 @@ import { Platform } from './taskutil';
1111
import * as toolUtil from './toolutil';
1212

1313
interface TaskParameters {
14-
readonly versionSpec: string,
15-
readonly addToPath: boolean
14+
versionSpec: string,
15+
addToPath: boolean,
16+
architecture: string
1617
}
1718

1819
export function pythonVersionToSemantic(versionSpec: string) {
1920
const prereleaseVersion = /(\d+\.\d+\.\d+)([a|b|rc]\d*)/g;
2021
return versionSpec.replace(prereleaseVersion, '$1-$2');
2122
}
2223

23-
export async function usePythonVersion(parameters: TaskParameters, platform: Platform): Promise<void> {
24+
export async function usePythonVersion(parameters: Readonly<TaskParameters>, platform: Platform): Promise<void> {
2425
// Python's prelease versions look like `3.7.0b2`.
2526
// This is the one part of Python versioning that does not look like semantic versioning, which specifies `3.7.0-b2`.
2627
// If the version spec contains prerelease versions, we need to convert them to the semantic version equivalent
2728
const semanticVersionSpec = pythonVersionToSemantic(parameters.versionSpec);
2829
task.debug(`Semantic version spec of ${parameters.versionSpec} is ${semanticVersionSpec}`);
2930

30-
const installDir: string | null = tool.findLocalTool('Python', semanticVersionSpec);
31+
const installDir: string | null = tool.findLocalTool('Python', semanticVersionSpec, parameters.architecture);
3132
if (!installDir) {
3233
// Fail and list available versions
34+
const x86Versions = tool.findLocalToolVersions('Python', 'x86')
35+
.map(s => `${s} (x86)`)
36+
.join(os.EOL);
37+
38+
const x64Versions = tool.findLocalToolVersions('Python', 'x64')
39+
.map(s => `${s} (x64)`)
40+
.join(os.EOL);
41+
3342
throw new Error([
3443
task.loc('VersionNotFound', parameters.versionSpec),
3544
task.loc('ListAvailableVersions'),
36-
tool.findLocalToolVersions('Python')
45+
x86Versions,
46+
x64Versions
3747
].join(os.EOL));
3848
}
3949

4050
task.setVariable('pythonLocation', installDir);
4151
if (parameters.addToPath) {
4252
toolUtil.prependPathSafe(installDir);
4353

44-
// Python has "scripts" directories where command-line tools that come with packages are installed.
45-
// There are different directories for `pip install` and `pip install --user`.
46-
// On Linux and macOS, pip will create the scripts directories and add them to PATH as needed.
47-
// On Windows, these directories do not get added to PATH, so we will add them ourselves.
54+
// Make sure Python's "bin" directories are in PATH.
55+
// Python has "scripts" or "bin" directories where command-line tools that come with packages are installed.
56+
// This is where pip is, along with anything that pip installs.
57+
// There is a seperate directory for `pip install --user`.
58+
//
4859
// For reference, these directories are as follows:
4960
// macOS / Linux:
50-
// /usr/local/bin
61+
// <sys.prefix>/bin (by default /usr/local/bin, but not on hosted agents -- see the `else`)
5162
// (--user) ~/.local/bin
5263
// Windows:
5364
// <Python installation dir>\Scripts
5465
// (--user) %APPDATA%\Python\PythonXY\Scripts
66+
// See https://docs.python.org/3/library/sysconfig.html
5567
if (platform === Platform.Windows) {
68+
// On Windows, these directories do not get added to PATH, so we will add them ourselves.
5669
const scriptsDir = path.join(installDir, 'Scripts');
5770
toolUtil.prependPathSafe(scriptsDir);
5871

@@ -65,6 +78,13 @@ export async function usePythonVersion(parameters: TaskParameters, platform: Pla
6578

6679
const userScriptsDir = path.join(process.env['APPDATA'], 'Python', `Python${major}${minor}`, 'Scripts');
6780
toolUtil.prependPathSafe(userScriptsDir);
81+
} else {
82+
// On Linux and macOS, tools cache should be set up so that each Python version has its own "bin" directory.
83+
// We do this so that the tool cache can just be dropped on an agent with minimal installation (no copying to /usr/local).
84+
// This allows us side-by-side the same minor version of Python with different patch versions or architectures (since Python uses /usr/local/lib/python3.6, etc.).
85+
toolUtil.prependPathSafe(path.join(installDir, 'bin'));
86+
87+
// On Linux and macOS, pip will create the --user directory and add it to PATH as needed.
6888
}
6989
}
7090
}

0 commit comments

Comments
 (0)