Skip to content

Commit d503642

Browse files
authored
Fix issue with tool cache on self-hosted runners (#152)
* Add failing test * Always call mpm install even if MATLAB is in toolcache * Disable Windows installation tests due to symlink issues with toolcache on Microsoft-hosted agents * Add test running InstallMATLAB twice with no additional products * Enhance MATLAB installation handling to ignore 'already installed' messages and update test cases accordingly
1 parent 1aead4d commit d503642

File tree

5 files changed

+93
-9
lines changed

5 files changed

+93
-9
lines changed

integ-test-promote-template.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,53 @@ jobs:
6060
inputs:
6161
command: assert(strcmp(version('-release'),'2023a'))
6262

63+
- job: test_install_with_toolcache_v${{ version }}
64+
condition: not(eq(${{ version }}, '0'))
65+
strategy:
66+
matrix:
67+
microsoft_hosted_linux:
68+
poolName: Azure Pipelines
69+
vmImage: ubuntu-22.04
70+
microsoft_hosted_macos:
71+
poolName: Azure Pipelines
72+
vmImage: macOS-latest
73+
# Installing with toolcache on Windows cannot be tested on Microsoft-hosted agents
74+
# because InstallMATLAB sets up a symlinked toolcache for performance reasons and MPM
75+
# does not play well with symlinks on Windows.
76+
# microsoft_hosted_windows:
77+
# poolName: Azure Pipelines
78+
# vmImage: windows-latest
79+
pool:
80+
name: $(poolName)
81+
vmImage: $(vmImage)
82+
steps:
83+
- checkout: none
84+
- task: MathWorks.matlab-azure-devops-extension-dev.InstallMATLAB.InstallMATLAB@${{ version }}
85+
displayName: Install MATLAB
86+
inputs:
87+
release: R2023a
88+
- task: MathWorks.matlab-azure-devops-extension-dev.RunMATLABCommand.RunMATLABCommand@${{ version }}
89+
displayName: Check Image Processing Toolbox is NOT installed
90+
inputs:
91+
command: assert(~any(strcmp({ver().Name},'Image Processing Toolbox')))
92+
- task: MathWorks.matlab-azure-devops-extension-dev.InstallMATLAB.InstallMATLAB@${{ version }}
93+
displayName: Install MATLAB no-ops on second run without additional products
94+
inputs:
95+
release: R2023a
96+
- task: MathWorks.matlab-azure-devops-extension-dev.RunMATLABCommand.RunMATLABCommand@${{ version }}
97+
displayName: Check Image Processing Toolbox is NOT installed
98+
inputs:
99+
command: assert(~any(strcmp({ver().Name},'Image Processing Toolbox')))
100+
- task: MathWorks.matlab-azure-devops-extension-dev.InstallMATLAB.InstallMATLAB@${{ version }}
101+
displayName: Install MATLAB installs additional products
102+
inputs:
103+
release: R2023a
104+
products: Image_Processing_Toolbox
105+
- task: MathWorks.matlab-azure-devops-extension-dev.RunMATLABCommand.RunMATLABCommand@${{ version }}
106+
displayName: Check Image Processing Toolbox is installed
107+
inputs:
108+
command: assert(any(strcmp({ver().Name},'Image Processing Toolbox')))
109+
63110
- job: test_install_latest_including_prerelease_v${{ version }}
64111
condition: not(eq(${{ version }}, '0'))
65112
strategy:

tasks/install-matlab/v1/src/install.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,8 @@ export async function install(platform: string, architecture: string, release: s
2828
const mpmPath: string = await mpm.setup(platform, matlabArch);
2929

3030
// install MATLAB using mpm
31-
const [toolpath, alreadyExists] = await matlab.makeToolcacheDir(parsedRelease, platform);
32-
if (!alreadyExists) {
33-
await mpm.install(mpmPath, parsedRelease, toolpath, products);
34-
}
31+
const [toolpath] = await matlab.makeToolcacheDir(parsedRelease, platform);
32+
await mpm.install(mpmPath, parsedRelease, toolpath, products);
3533

3634
// add MATLAB to system path
3735
try {

tasks/install-matlab/v1/src/mpm.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright 2023-2024 The MathWorks, Inc.
22

33
import * as taskLib from "azure-pipelines-task-lib/task";
4+
import * as trm from "azure-pipelines-task-lib/toolrunner";
5+
import { Writable } from "stream";
46
import * as matlab from "./matlab";
57
import { downloadToolWithRetries } from "./utils";
68

@@ -66,8 +68,25 @@ export async function install(
6668
mpmArguments = mpmArguments.concat("--products");
6769
mpmArguments = mpmArguments.concat(parsedProducts);
6870

69-
const exitCode = await taskLib.exec(mpmPath, mpmArguments);
70-
if (exitCode !== 0) {
71+
let output = "";
72+
const captureStream = new Writable({
73+
write(chunk, encoding, callback) {
74+
const text = chunk.toString();
75+
output += text;
76+
process.stdout.write(text);
77+
callback();
78+
},
79+
});
80+
81+
const options: trm.IExecOptions = {
82+
outStream: captureStream,
83+
errStream: captureStream,
84+
ignoreReturnCode: true,
85+
};
86+
87+
const exitCode = await taskLib.exec(mpmPath, mpmArguments, options);
88+
89+
if (exitCode !== 0 && !output.toLowerCase().includes("already installed")) {
7190
return Promise.reject(Error(`Failed to install MATLAB.`));
7291
}
7392
return;

tasks/install-matlab/v1/test/install.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,14 @@ export default function suite() {
8383
assert(stubInstallSystemDependencies.notCalled);
8484
});
8585

86-
it("does not install if MATLAB already exists in toolcache", async () => {
86+
it("re-calls MPM install even if MATLAB already exists in toolcache", async () => {
8787
stubMakeToolcacheDir.callsFake((rel) => {
8888
return [toolcacheDir, true];
8989
});
9090
await assert.doesNotReject(async () => {
9191
await install.install(platform, architecture, release, products);
9292
});
93-
assert(stubMpmInstall.notCalled);
93+
assert(stubMpmInstall.calledWith(sinon.match.any, releaseInfo, toolcacheDir, products));
9494
});
9595

9696
it("fails if add to path fails", async () => {

tasks/install-matlab/v1/test/mpm.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,27 @@ export default function suite() {
162162
// non-zero exit code
163163
return Promise.resolve(1);
164164
});
165-
assert.rejects(async () => mpm.install(mpmPath, releaseInfo, destination, products));
165+
await assert.rejects(async () => mpm.install(mpmPath, releaseInfo, destination, products));
166+
});
167+
168+
it("install does not fail when mpm reports products already installed", async () => {
169+
const mpmPath = "mpm";
170+
const releaseInfo = {name: "r2022b", version: "9.13.0", update: "Latest", isPrerelease: false};
171+
const destination = "/opt/matlab";
172+
const products = "MATLAB Compiler";
173+
174+
// Simulate mpm returning a non-zero exit code but writing the
175+
// "already installed" message to the outStream so install should succeed.
176+
stubExec.callsFake((bin, args?, options?) => {
177+
if (options && options.outStream && typeof options.outStream.write === "function") {
178+
options.outStream.write("All specified products are already installed.\n");
179+
}
180+
return Promise.resolve(1);
181+
});
182+
183+
// Should not reject
184+
await mpm.install(mpmPath, releaseInfo, destination, products);
185+
assert(stubExec.called);
166186
});
167187
});
168188
}

0 commit comments

Comments
 (0)