-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathmain.js
More file actions
204 lines (177 loc) · 8.76 KB
/
main.js
File metadata and controls
204 lines (177 loc) · 8.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
const os = require('os');
const path = require('path');
const fs = require('fs').promises;
const core = require('@actions/core');
const tc = require('@actions/tool-cache');
const cache = require('@actions/cache');
const exec = require('@actions/exec');
const common = require('./common');
const minisign = require('./minisign');
// Upstream's minisign key, from https://ziglang.org/download
const MINISIGN_KEY = 'RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U';
// The base URL of the official builds of Zig. This is only used as a fallback, if all mirrors fail.
const CANONICAL_DEV = 'https://ziglang.org/builds';
const CANONICAL_RELEASE = 'https://ziglang.org/download';
// The URL of the mirror list. This should be an ASCII-encoded text file, with one mirror per LF-separated line.
const MIRRORS_URL = 'https://ziglang.org/download/community-mirrors.txt';
async function downloadFromMirror(mirror, tarball_filename) {
const tarball_path = await tc.downloadTool(`${mirror}/${tarball_filename}?source=github-mlugg-setup-zig`);
const signature_response = await fetch(`${mirror}/${tarball_filename}.minisig?source=github-mlugg-setup-zig`);
const signature_data = Buffer.from(await signature_response.arrayBuffer());
const tarball_data = await fs.readFile(tarball_path);
const key = await minisign.parseKey(MINISIGN_KEY);
const signature = minisign.parseSignature(signature_data);
if (!await minisign.verifySignature(key, signature, tarball_data)) {
throw new Error(`signature verification failed for '${mirror}/${tarball_filename}'`);
}
// Parse the trusted comment to validate the tarball name.
// This prevents a malicious actor from trying to pass off one signed tarball as another.
const match = /^timestamp:\d+\s+file:([^\s]+)\s+hashed$/.exec(signature.trusted_comment.toString());
if (match === null || match[1] !== tarball_filename) {
throw new Error(`filename verification failed for '${mirror}/${tarball_filename}'`);
}
return tarball_path;
}
async function downloadTarball(tarball_filename) {
const preferred_mirror = core.getInput('mirror');
if (preferred_mirror.includes("://ziglang.org/") || preferred_mirror.startsWith("ziglang.org/")) {
throw new Error("'https://ziglang.org' cannot be used as mirror override; for more information see README.md");
}
if (preferred_mirror) {
core.info(`Using mirror: ${preferred_mirror}`);
return await downloadFromMirror(preferred_mirror, tarball_filename);
}
let mirrors;
try {
// Fetch an up-to-date list of available mirrors from ziglang.org.
const mirrors_response = await fetch(MIRRORS_URL);
mirrors = (await mirrors_response.text()).split('\n').filter((url) => url.length != 0);
} catch {
// ziglang.org itself is inaccessible. As a fallback (so that ziglang.org outages don't break
// this Action), use a hardcoded mirror list. This was manually fetched from ziglang.org, and I
// expect that at least some of these mirrors will continue working. It only needs to be updated
// if the upstream list changes *significantly*.
mirrors = [
"https://pkg.machengine.org/zig",
"https://zigmirror.hryx.net/zig",
"https://zig.linus.dev/zig",
"https://zig.squirl.dev",
"https://zig.florent.dev",
"https://zig.mirror.mschae23.de/zig",
"https://zigmirror.meox.dev",
"https://ziglang.freetls.fastly.net",
"https://zig.tilok.dev",
"https://zig-mirror.tsimnet.eu/zig",
"https://zig.karearl.com/zig",
"https://pkg.earth/zig",
"https://fs.liujiacai.net/zigbuilds",
];
}
core.info(`Available mirrors: ${mirrors.join(", ")}`);
// We will attempt all mirrors before making a last-ditch attempt to the official download.
// To avoid hammering a single mirror, we first randomize the array.
const shuffled_mirrors = mirrors.map((m) => [m, Math.random()]).sort((a, b) => a[1] - b[1]).map((a) => a[0]);
for (const mirror of shuffled_mirrors) {
core.info(`Attempting mirror: ${mirror}`);
try {
return await downloadFromMirror(mirror, tarball_filename);
} catch (e) {
core.info(`Mirror failed with error: ${e}`);
// continue loop to next mirror
}
}
// As a fallback, attempt ziglang.org.
const zig_version = tarball_filename.match(/\d+\.\d+\.\d+(-dev\.\d+\+[0-9a-f]+)?/)[0];
const canonical = zig_version.includes("-dev") ? CANONICAL_DEV : `${CANONICAL_RELEASE}/${zig_version}`;
core.info(`Attempting official: ${canonical}`);
return await downloadFromMirror(canonical, tarball_filename);
}
async function retrieveTarball(tarball_name, tarball_ext) {
const cache_key = `setup-zig-tarball-${tarball_name}`;
const tarball_basename = `${tarball_name}${tarball_ext}`;
const tarball_cache_path = path.join(process.env['RUNNER_TEMP'], tarball_basename);
if (await cache.restoreCache([tarball_cache_path], cache_key)) {
return tarball_cache_path;
}
core.info(`Cache miss. Fetching Zig ${await common.getVersion()}`);
const downloaded_path = await downloadTarball(tarball_basename);
await fs.copyFile(downloaded_path, tarball_cache_path)
await cache.saveCache([tarball_cache_path], cache_key);
return tarball_cache_path;
}
async function main() {
try {
// We will check whether Zig is stored in the cache. We use two separate caches.
// * 'tool-cache' caches the final extracted directory if the same Zig build is used multiple
// times by one job. We have this dependency anyway for archive extraction.
// * 'cache' only caches the unextracted archive, but it does so across runs. It's a little
// less efficient, but still much preferable to fetching Zig from a mirror. We have this
// dependency anyway for caching the global Zig cache.
//
// Unfortunately, tool-cache can lead to serious performance problems on GitHub-hosted Actions
// runners -- or their Windows ones at least, because the tool cache is stored on a slow drive.
// There are even hacky workarounds for this in official Actions:
//
// https://github.com/actions/setup-go/blob/d35c59abb061a4a6fb18e82ac0862c26744d6ab5/src/installer.ts#L174
//
// Since tool-cache is only really useful on self-hosted runners, let's just disable it by
// default on GitHub-hosted runners, and hence execute Zig straight out of its extracted dir.
let use_tool_cache = core.getInput('use-tool-cache');
if (use_tool_cache === 'true') {
use_tool_cache = true;
} else if (use_tool_cache === 'false') {
use_tool_cache = false;
} else if (use_tool_cache === '') {
use_tool_cache = process.env['RUNNER_ENVIRONMENT'] !== 'github-hosted';
} else {
throw new Error("Invalid 'use-tool-cache' value. Valid values: 'true', 'false'");
}
core.info(`Using tool-cache: ${use_tool_cache}`);
let zig_dir;
if (use_tool_cache) {
zig_dir = tc.find('zig', await common.getVersion());
}
if (zig_dir) {
core.info('Using cached Zig installation from tool-cache');
} else {
const tarball_name = await common.getTarballName();
const tarball_ext = await common.getTarballExt();
core.info(`Fetching ${tarball_name}${tarball_ext}`);
const fetch_start = Date.now();
const tarball_path = await retrieveTarball(tarball_name, tarball_ext);
core.info(`Fetch took ${Date.now() - fetch_start} ms`);
core.info(`Extracting tarball ${tarball_name}${tarball_ext}`);
const extract_start = Date.now();
const zig_parent_dir = tarball_ext === '.zip' ?
await tc.extractZip(tarball_path) :
await tc.extractTar(tarball_path, null, 'xJ'); // J for xz
core.info(`Extract took ${Date.now() - extract_start} ms`);
const zig_inner_dir = path.join(zig_parent_dir, tarball_name);
if (use_tool_cache) {
core.info('Copying Zig installation to tool-cache');
zig_dir = await tc.cacheDir(zig_inner_dir, 'zig', await common.getVersion());
} else {
zig_dir = zig_inner_dir;
}
}
core.addPath(zig_dir);
const zig_version = (await exec.getExecOutput('zig', ['version'])).stdout.trim();
core.info(`Resolved Zig version ${zig_version}`);
const cache_path = common.getZigCachePath();
core.exportVariable('ZIG_GLOBAL_CACHE_DIR', cache_path);
core.exportVariable('ZIG_LOCAL_CACHE_DIR', cache_path);
if (core.getBooleanInput('use-cache')) {
const cache_prefix = await common.getCachePrefix();
core.info(`Attempting restore of Zig cache with prefix '${cache_prefix}'`);
const hit = await cache.restoreCache([cache_path], cache_prefix);
if (hit === undefined) {
core.info(`Cache miss: leaving Zig cache directory at ${cache_path} unpopulated`);
} else {
core.info(`Cache hit (key '${hit}'): populating Zig cache directory at ${cache_path}`);
}
}
} catch (err) {
core.setFailed(err.message);
}
}
main();