Skip to content

Commit 5494089

Browse files
committed
Cache the build of shards, make the path predictable
1 parent 0c693f8 commit 5494089

File tree

4 files changed

+467
-79
lines changed

4 files changed

+467
-79
lines changed

action.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ inputs:
1515
outputs:
1616
crystal:
1717
description: The actual version of Crystal that was installed
18+
path:
19+
description: The directory where Crystal was installed
1820
runs:
1921
using: node12
2022
main: index.js

index.js

Lines changed: 88 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const Core = require("@actions/core");
22
const ToolCache = require("@actions/tool-cache");
3+
const Cache = require("@actions/cache");
34
const IO = require("@actions/io");
45
const Octokit = require("@octokit/request");
56
const fetch = require("node-fetch");
@@ -13,15 +14,23 @@ const execFile = Util.promisify(ChildProcess.execFile);
1314

1415
async function run() {
1516
try {
16-
const params = getPlatform() === Windows ?
17-
{crystal: "nightly", shards: "true"} :
18-
{crystal: "latest", shards: "true"};
19-
for (const key of ["crystal", "shards", "arch", "destination"]) {
17+
const params = {
18+
"crystal": getPlatform() === Windows ? "nightly" : "latest",
19+
"shards": "true",
20+
};
21+
for (const key of ["crystal", "shards", "arch"]) {
2022
let value;
2123
if ((value = Core.getInput(key))) {
2224
params[key] = value;
2325
}
2426
}
27+
params.path = Core.getInput("destination") || Path.join(
28+
process.env["RUNNER_TEMP"], `crystal-${params.crystal}-${params.shards}-${params.arch}`,
29+
);
30+
if (params.shards === Any && getPlatform() === Windows) {
31+
params.shards = Latest;
32+
}
33+
2534
const func = {
2635
[Linux]: installCrystalForLinux,
2736
[Mac]: installCrystalForMac,
@@ -30,7 +39,10 @@ async function run() {
3039
if (!func) {
3140
throw `Platform "${getPlatform()}" is not supported`;
3241
}
33-
await maybeInstallShards(params, func(params));
42+
const crystalPromise = func(params);
43+
params.path += "-shards";
44+
await maybeInstallShards(params, crystalPromise);
45+
await crystalPromise;
3446

3547
const {stdout} = await subprocess(["crystal", "--version"]);
3648
Core.info(stdout);
@@ -82,20 +94,15 @@ async function subprocess(command, options) {
8294
return execFile(file, args, options);
8395
}
8496

85-
async function installCrystalForLinux({
86-
crystal,
87-
shards,
88-
arch = getArch(),
89-
destination = null,
90-
}) {
97+
async function installCrystalForLinux({crystal, shards, arch = getArch(), path}) {
9198
checkVersion(crystal, [Latest, Nightly, NumericVersion]);
9299
const suffixes = {"x86_64": "linux-x86_64", "x86": "linux-i686"};
93100
checkArch(arch, Object.keys(suffixes));
94101

95102
const depsTask = installAptPackages(
96103
"libevent-dev libgmp-dev libpcre3-dev libssl-dev libxml2-dev libyaml-dev".split(" "),
97104
);
98-
const path = await installBinaryRelease({crystal, shards, suffix: suffixes[arch], destination});
105+
await installBinaryRelease({crystal, shards, suffix: suffixes[arch], path});
99106

100107
Core.info("Setting up environment for Crystal");
101108
Core.addPath(Path.join(path, "bin"));
@@ -107,17 +114,10 @@ async function installCrystalForLinux({
107114
await depsTask;
108115
}
109116

110-
async function installCrystalForMac({
111-
crystal,
112-
shards,
113-
arch = "x86_64",
114-
destination = null,
115-
}) {
117+
async function installCrystalForMac({crystal, shards, arch = "x86_64", path}) {
116118
checkVersion(crystal, [Latest, Nightly, NumericVersion]);
117119
checkArch(arch, ["x86_64"]);
118-
const path = await installBinaryRelease({
119-
crystal, shards, suffix: "darwin-x86_64", destination,
120-
});
120+
await installBinaryRelease({crystal, shards, suffix: "darwin-x86_64", path});
121121

122122
Core.info("Setting up environment for Crystal");
123123
Core.addPath(Path.join(path, "embedded", "bin"));
@@ -149,53 +149,67 @@ async function installAptPackages(packages) {
149149
Core.endGroup();
150150
}
151151

152-
async function installBinaryRelease({crystal, suffix, destination}) {
152+
async function installBinaryRelease({crystal, suffix, path}) {
153153
if (crystal === Nightly) {
154-
return downloadCrystalNightly(suffix, destination);
154+
await IO.mv(await downloadCrystalNightly(suffix), path);
155155
} else {
156156
if (crystal === Latest) {
157157
crystal = null;
158158
}
159-
return downloadCrystalRelease(suffix, crystal, destination);
159+
await IO.mv(await downloadCrystalRelease(suffix, crystal), path);
160160
}
161161
}
162162

163-
async function maybeInstallShards({shards, destination}, crystalPromise) {
163+
async function maybeInstallShards({shards, path}, crystalPromise) {
164164
const allowed = [Latest, Nightly, NumericVersion, Any, None];
165-
if (getPlatform() === Windows && shards === Any) {
166-
shards = Latest;
167-
}
168165
checkVersion(shards, allowed);
169166
if (![Any, None].includes(shards)) {
170-
if (destination) {
171-
destination = Path.join(destination, "shards");
172-
}
173-
await installShards({shards, destination}, crystalPromise);
174-
} else {
175-
await crystalPromise;
167+
await installShards({shards, path}, crystalPromise);
176168
}
177169
if (shards !== None) {
170+
await crystalPromise;
178171
const {stdout} = await subprocess(["shards", "--version"]);
179172
Core.info(stdout);
180173
}
181174
}
182175

183-
async function installShards({shards, destination}, crystalPromise) {
176+
async function installShards({shards, path}, crystalPromise) {
184177
if (NumericVersion.test(shards)) {
185178
shards = "v" + shards;
186179
}
187-
const {ref, path} = await downloadSource({
188-
name: "Shards", apiBase: GitHubApiBaseShards, version: shards, destination,
189-
});
180+
const ref = await findRef({name: "Shards", apiBase: GitHubApiBaseShards, version: shards});
190181
Core.setOutput("shards", ref);
191-
await crystalPromise;
192182

193-
Core.info("Building Shards");
194-
const {stdout} = await subprocess(["make"], {cwd: path});
195-
Core.startGroup("Finished building Shards");
196-
Core.info(stdout);
197-
Core.endGroup();
183+
const cacheKey = `install-shards-v1-${ref}--${getArch()}-${getPlatform()}`;
184+
let restored = null;
185+
try {
186+
Core.info(`Trying to restore cache: key '${cacheKey}'`);
187+
restored = await Cache.restoreCache([path], cacheKey);
188+
} catch (error) {
189+
Core.warning(error.message);
190+
}
191+
if (!restored) {
192+
Core.info(`Cache not found for key '${cacheKey}'`);
193+
const fetchSrcTask = downloadSource({name: "Shards", apiBase: GitHubApiBaseShards, ref});
194+
await IO.mv(await fetchSrcTask, path);
195+
await crystalPromise;
198196

197+
Core.info("Building Shards");
198+
const {stdout} = await subprocess(["make"], {cwd: path});
199+
Core.startGroup("Finished building Shards");
200+
Core.info(stdout);
201+
Core.endGroup();
202+
}
203+
if (restored !== cacheKey) {
204+
Core.info(`Saving cache: '${cacheKey}'`);
205+
try {
206+
await Cache.saveCache([path], cacheKey);
207+
} catch (error) {
208+
Core.warning(error.message);
209+
}
210+
}
211+
212+
await crystalPromise;
199213
Core.info("Setting up environment for Shards");
200214
Core.addPath(Path.join(path, "bin"));
201215
}
@@ -225,7 +239,7 @@ async function findLatestCommit({name, apiBase, branch = "master"}) {
225239
return commit["sha"];
226240
}
227241

228-
async function downloadCrystalRelease(suffix, version = null, destination = null) {
242+
async function downloadCrystalRelease(suffix, version = null) {
229243
const release = await findRelease({name: "Crystal", apiBase: GitHubApiBase, version});
230244
Core.setOutput("crystal", release["tag_name"]);
231245

@@ -238,30 +252,31 @@ async function downloadCrystalRelease(suffix, version = null, destination = null
238252
});
239253

240254
Core.info("Extracting Crystal build");
241-
return onlySubdir(await ToolCache.extractTar(downloadedPath, destination));
255+
const extractedPath = await ToolCache.extractTar(downloadedPath);
256+
return onlySubdir(extractedPath);
242257
}
243258

244-
async function downloadSource({name, apiBase, version = null, destination = null}) {
245-
let ref = version;
259+
async function findRef({name, apiBase, version}) {
246260
if (version === Nightly) {
247-
ref = await findLatestCommit({name, apiBase});
261+
return findLatestCommit({name, apiBase});
248262
} else if (version === Latest) {
249263
const release = await findRelease({name, apiBase});
250-
ref = release["tag_name"];
264+
return release["tag_name"];
251265
}
266+
return version;
267+
}
252268

269+
async function downloadSource({name, apiBase, ref}) {
253270
Core.info(`Downloading ${name} source for ${ref}`);
254271
const downloadedPath = await githubDownloadViaRedirect({
255272
url: apiBase + "/zipball/:ref",
256273
"ref": ref,
257274
});
258275
Core.info(`Extracting ${name} source`);
259-
const path = await onlySubdir(await ToolCache.extractZip(downloadedPath, destination));
260-
261-
return {ref, path};
276+
return onlySubdir(await ToolCache.extractZip(downloadedPath));
262277
}
263278

264-
async function downloadCrystalNightly(suffix, destination = null) {
279+
async function downloadCrystalNightly(suffix) {
265280
Core.info("Looking for latest Crystal build");
266281

267282
let build;
@@ -289,18 +304,19 @@ async function downloadCrystalNightly(suffix, destination = null) {
289304
Core.info(`Downloading Crystal build from ${artifact["url"]}`);
290305
const downloadedPath = await ToolCache.downloadTool(artifact["url"]);
291306
Core.info("Extracting Crystal build");
292-
return onlySubdir(await ToolCache.extractTar(downloadedPath, destination));
307+
const extractedPath = await ToolCache.extractTar(downloadedPath);
308+
return onlySubdir(extractedPath);
293309
}
294310

295-
async function installCrystalForWindows({crystal, arch = "x86_64", destination = null}) {
311+
async function installCrystalForWindows({crystal, arch = "x86_64", path}) {
296312
checkVersion(crystal, [Nightly]);
297313
checkArch(arch, ["x86_64"]);
298-
const path = await downloadCrystalNightlyForWindows(destination);
314+
await IO.mv(await downloadCrystalNightlyForWindows(), path);
299315

300316
Core.info("Setting up environment for Crystal");
301317
const vars = await variablesForVCBuildTools();
302-
addPathToVars(vars, "PATH", path);
303-
addPathToVars(vars, "LIB", path);
318+
addPathToVars(vars, "PATH", Path.join(path, "bin"));
319+
addPathToVars(vars, "LIB", Path.join(path, "bin"));
304320
addPathToVars(vars, "CRYSTAL_PATH", Path.join(path, "src"));
305321
addPathToVars(vars, "CRYSTAL_PATH", "lib");
306322
for (const [k, v] of vars.entries()) {
@@ -348,39 +364,40 @@ function* getChangedVars(lines) {
348364
}
349365
}
350366

351-
async function downloadCrystalNightlyForWindows(destination = null) {
367+
async function downloadCrystalNightlyForWindows() {
352368
Core.info("Looking for latest Crystal build");
353369

354370
const runsResp = await githubGet({
355371
url: GitHubApiBase + "/actions/workflows/win.yml/runs?branch=master&event=push&status=success",
356372
"per_page": 1,
357373
});
358374
const [workflowRun] = runsResp.data["workflow_runs"];
359-
const {"head_sha": version, "id": runId} = workflowRun;
375+
const {"head_sha": ref, "id": runId} = workflowRun;
360376
Core.info(`Found Crystal release ${workflowRun["html_url"]}`);
361-
Core.setOutput("crystal", version);
377+
Core.setOutput("crystal", ref);
362378

363-
const fetchExeTask = async () => {
379+
const fetchSrcTask = downloadSource({name: "Crystal", apiBase: GitHubApiBase, ref});
380+
const fetchExeTask = (async () => {
364381
const artifactsResp = await githubGet({
365382
url: GitHubApiBase + "/actions/runs/:run_id/artifacts",
366383
"run_id": runId,
367384
});
368385
const artifact = artifactsResp.data["artifacts"].find((x) => x.name === "crystal");
369386

370387
Core.info("Downloading Crystal build");
371-
return githubDownloadViaRedirect({
388+
const downloadedPath = await githubDownloadViaRedirect({
372389
url: GitHubApiBase + "/actions/artifacts/:artifact_id/zip",
373390
"artifact_id": artifact.id,
374391
});
375-
};
376392

377-
const [{path: srcPath}, exeDownloadedPath] = await Promise.all([
378-
downloadSource({name: "Crystal", apiBase: GitHubApiBase, version, destination}),
379-
fetchExeTask(),
380-
]);
393+
Core.info("Extracting Crystal build");
394+
return ToolCache.extractZip(downloadedPath);
395+
})();
381396

382-
Core.info("Extracting Crystal build");
383-
return ToolCache.extractZip(exeDownloadedPath, srcPath);
397+
const path = await fetchSrcTask;
398+
await IO.rmRF(Path.join(path, "bin"));
399+
await IO.mv(await fetchExeTask, Path.join(path, "bin"));
400+
return path;
384401
}
385402

386403
function githubGet(request) {

0 commit comments

Comments
 (0)