Skip to content

Commit 79d3179

Browse files
committed
Implement installing Shards
1 parent f6ea824 commit 79d3179

File tree

5 files changed

+189
-54
lines changed

5 files changed

+189
-54
lines changed

.eslintrc.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"ecmaVersion": 2018
1515
},
1616
"rules": {
17+
"no-empty": ["error", {"allowEmptyCatch": true}],
18+
1719
"block-scoped-var": "error",
1820
"consistent-return": "error",
1921
"curly": "error",

.github/workflows/main.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,37 @@ jobs:
1616
steps:
1717
- uses: actions/checkout@v2
1818
- run: npm install
19+
20+
- uses: ./
21+
with:
22+
shards: false
23+
- run: crystal eval "puts 1337"
24+
- run: '! shards --version'
25+
26+
- uses: ./
27+
with:
28+
shards: true
29+
- run: crystal eval "puts 1337"
30+
- run: shards --version
31+
1932
- uses: ./
33+
with:
34+
shards: latest
35+
if: runner.os != 'Windows'
2036
- run: crystal eval "puts 1337"
37+
if: runner.os != 'Windows'
2138
- run: shards --version
2239
if: runner.os != 'Windows'
40+
41+
- uses: ./
42+
with:
43+
shards: nightly
44+
- run: crystal eval "puts 1337"
45+
- run: shards --version
46+
2347
- uses: ./
2448
with:
2549
crystal: nightly
50+
2651
- run: npm test
2752
- run: npm audit --audit-level=moderate

.github/workflows/release.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,32 @@ jobs:
1616
runs-on: ${{ matrix.os }}
1717
steps:
1818
- uses: oprypin/install-crystal@v1
19+
with:
20+
shards: false
21+
- run: crystal eval "puts 1337"
22+
- run: '! shards --version'
23+
24+
- uses: oprypin/install-crystal@v1
25+
with:
26+
shards: true
27+
- run: crystal eval "puts 1337"
28+
- run: shards --version
29+
30+
- uses: oprypin/install-crystal@v1
31+
with:
32+
shards: latest
33+
if: runner.os != 'Windows'
1934
- run: crystal eval "puts 1337"
35+
if: runner.os != 'Windows'
36+
- run: shards --version
37+
if: runner.os != 'Windows'
38+
39+
- uses: oprypin/install-crystal@v1
40+
with:
41+
shards: nightly
42+
- run: crystal eval "puts 1337"
43+
- run: shards --version
44+
2045
- uses: oprypin/install-crystal@v1
2146
with:
2247
crystal: nightly

action.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ name: Install Crystal
22
description: Install Crystal programming language
33
inputs:
44
crystal:
5-
description: The version of crystal to install ("latest", "nightly", "0.34.0" etc)
5+
description: The version of Crystal to install ("latest", "nightly", "0.34.0" etc)
6+
shards:
7+
description: The version of Shards to install (true, "latest", "nightly", "0.12.0" etc)
68
arch:
79
description: The architecture of the build of Crystal ("x86_64")
810
destination:

index.js

Lines changed: 134 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ const execFile = Util.promisify(ChildProcess.execFile);
1212

1313
async function run() {
1414
try {
15-
const params = {};
16-
for (const key of ["crystal", "arch", "destination"]) {
15+
const params = getPlatform() === Windows ?
16+
{crystal: "nightly", shards: "false"} :
17+
{crystal: "latest", shards: "true"};
18+
for (const key of ["crystal", "shards", "arch", "destination"]) {
1719
let value;
1820
if ((value = Core.getInput(key))) {
1921
params[key] = value;
@@ -27,7 +29,7 @@ async function run() {
2729
if (!func) {
2830
throw `Platform "${getPlatform()}" is not supported`;
2931
}
30-
await func(params);
32+
await maybeInstallShards(params, func(params));
3133

3234
Core.info("[command]crystal --version");
3335
const {stdout} = await execFile("crystal", ["--version"]);
@@ -57,10 +59,12 @@ function checkArch(arch, allowed) {
5759

5860
const Latest = "latest";
5961
const Nightly = "nightly";
62+
const Any = "true";
63+
const None = "false";
6064
const NumericVersion = /^\d[.\d]+\d$/;
6165

6266
function checkVersion(version, allowed) {
63-
const numericVersion = version.match(NumericVersion) && version;
67+
const numericVersion = NumericVersion.test(version) && version;
6468
allowed[allowed.indexOf(NumericVersion)] = numericVersion;
6569

6670
if (allowed.includes(version)) {
@@ -73,30 +77,50 @@ function checkVersion(version, allowed) {
7377
}
7478

7579
async function installCrystalForLinux({
76-
crystal = Latest,
80+
crystal,
81+
shards,
7782
arch = getArch(),
7883
destination = null,
7984
}) {
8085
checkVersion(crystal, [Latest, Nightly, NumericVersion]);
8186
const suffixes = {"x86_64": "linux-x86_64", "x86": "linux-i686"};
8287
checkArch(arch, Object.keys(suffixes));
8388

84-
return Promise.all([
85-
installAptPackages(
86-
"libevent-dev libgmp-dev libpcre3-dev libssl-dev libxml2-dev libyaml-dev".split(" "),
87-
),
88-
installBinaryRelease({crystal, suffix: suffixes[arch], destination}),
89-
]);
89+
const p = installAptPackages(
90+
"libevent-dev libgmp-dev libpcre3-dev libssl-dev libxml2-dev libyaml-dev".split(" "),
91+
);
92+
const path = await installBinaryRelease({crystal, shards, suffix: suffixes[arch], destination});
93+
94+
Core.info("Setting up environment for Crystal");
95+
Core.addPath(Path.join(path, "bin"));
96+
if (shards === None) {
97+
try {
98+
await FS.unlink(Path.join(path, "bin", "shards"));
99+
} catch (e) {}
100+
}
101+
await p;
90102
}
91103

92104
async function installCrystalForMac({
93-
crystal = Latest,
105+
crystal,
106+
shards,
94107
arch = "x86_64",
95108
destination = null,
96109
}) {
97110
checkVersion(crystal, [Latest, Nightly, NumericVersion]);
98111
checkArch(arch, ["x86_64"]);
99-
return installBinaryRelease({crystal, suffix: "darwin-x86_64", destination});
112+
const path = await installBinaryRelease({
113+
crystal, shards, suffix: "darwin-x86_64", destination,
114+
});
115+
116+
Core.info("Setting up environment for Crystal");
117+
Core.addPath(Path.join(path, "embedded", "bin"));
118+
Core.addPath(Path.join(path, "bin"));
119+
if (shards === None) {
120+
try {
121+
await FS.unlink(Path.join(path, "embedded", "bin", "shards"));
122+
} catch (e) {}
123+
}
100124
}
101125

102126
async function installAptPackages(packages) {
@@ -112,43 +136,121 @@ async function installAptPackages(packages) {
112136
}
113137

114138
async function installBinaryRelease({crystal, suffix, destination}) {
115-
let path;
116139
if (crystal === Nightly) {
117-
path = await downloadCrystalNightly(suffix, destination);
140+
return downloadCrystalNightly(suffix, destination);
118141
} else {
119142
if (crystal === Latest) {
120143
crystal = null;
121144
}
122-
path = await downloadCrystalRelease(suffix, crystal, destination);
145+
return downloadCrystalRelease(suffix, crystal, destination);
123146
}
147+
}
124148

125-
Core.info("Setting up environment");
126-
Core.addPath(Path.join(path, "embedded", "bin"));
149+
async function maybeInstallShards({shards, destination}, crystalPromise) {
150+
const allowed = [Nightly, NumericVersion, Any, None];
151+
if (getPlatform() !== Windows) {
152+
allowed.push(Latest);
153+
} else if (shards === Any) {
154+
shards = Nightly;
155+
}
156+
checkVersion(shards, allowed);
157+
if (![Any, None].includes(shards)) {
158+
if (destination) {
159+
destination = Path.join(destination, "shards");
160+
}
161+
await installShards({shards, destination}, crystalPromise);
162+
} else {
163+
await crystalPromise;
164+
}
165+
if (shards !== None) {
166+
Core.info("[command]shards --version");
167+
const {stdout} = await execFile("shards", ["--version"]);
168+
Core.info(stdout);
169+
}
170+
}
171+
172+
async function installShards({shards, destination}, crystalPromise) {
173+
if (NumericVersion.test(shards)) {
174+
shards = "v" + shards;
175+
}
176+
const {ref, path} = await downloadSource({
177+
name: "Shards", apiBase: GitHubApiBaseShards, version: shards, destination,
178+
});
179+
Core.setOutput("shards", ref);
180+
await crystalPromise;
181+
182+
Core.info("Building Shards");
183+
Core.info("[command]make");
184+
const {stdout} = await execFile("make", {cwd: path});
185+
Core.startGroup("Finished building Shards");
186+
Core.info(stdout);
187+
Core.endGroup();
188+
189+
Core.info("Setting up environment for Shards");
127190
Core.addPath(Path.join(path, "bin"));
128191
}
129192

130193
const GitHubApiBase = "/repos/crystal-lang/crystal";
194+
const GitHubApiBaseShards = "/repos/crystal-lang/shards";
131195
const CircleApiBase = "https://circleci.com/api/v1.1/project/github/crystal-lang/crystal";
132196

133-
async function downloadCrystalRelease(suffix, tag = null, destination = null) {
134-
Core.info("Looking for latest Crystal release");
135-
197+
async function findRelease({name, apiBase, tag}) {
198+
Core.info(`Looking for latest ${name} release`);
136199
const releasesResp = await githubGet({
137-
url: GitHubApiBase + "/releases/" + (tag ? "tags/" + tag : "latest"),
200+
url: apiBase + "/releases/" + (tag ? "tags/" + tag : "latest"),
138201
});
139202
const release = releasesResp.data;
140-
Core.info("Found " + release["html_url"]);
203+
Core.info(`Found ${name} release ${release["html_url"]}`);
204+
return release;
205+
}
206+
207+
async function findLatestCommit({name, apiBase, branch = "master"}) {
208+
Core.info(`Looking for latest ${name} commit`);
209+
const commitsResp = await githubGet({
210+
url: apiBase + "/commits/" + branch,
211+
});
212+
const commit = commitsResp.data;
213+
Core.info(`Found ${name} commit ${commit["html_url"]}`);
214+
return commit["sha"];
215+
}
216+
217+
async function downloadCrystalRelease(suffix, version = null, destination = null) {
218+
const release = await findRelease({name: "Crystal", apiBase: GitHubApiBase, version});
141219
Core.setOutput("crystal", release["tag_name"]);
142220

143221
const asset = release["assets"].find((a) => a["name"].endsWith([`-${suffix}.tar.gz`]));
144222

145-
Core.info("Downloading Crystal build");
223+
Core.info(`Downloading Crystal build from ${asset["browser_download_url"]}`);
146224
const downloadedPath = await ToolCache.downloadTool(asset["browser_download_url"]);
147225

148226
Core.info("Extracting Crystal build");
149227
return onlySubdir(await ToolCache.extractTar(downloadedPath, destination));
150228
}
151229

230+
async function downloadSource({name, apiBase, version = null, destination = null}) {
231+
let ref = version;
232+
if (version === Nightly) {
233+
ref = await findLatestCommit({name, apiBase});
234+
} else if (version === Latest) {
235+
const release = await findRelease({name, apiBase});
236+
ref = release["tag_name"];
237+
}
238+
239+
const zipballLinkResp = await githubGet({
240+
url: apiBase + "/zipball/:ref",
241+
"ref": ref,
242+
request: {redirect: "manual"},
243+
});
244+
const downloadUrl = zipballLinkResp.headers["location"];
245+
246+
Core.info(`Downloading ${name} source from ${downloadUrl}`);
247+
const downloadedPath = await ToolCache.downloadTool(downloadUrl);
248+
Core.info(`Extracting ${name} source`);
249+
const path = await onlySubdir(await ToolCache.extractZip(downloadedPath, destination));
250+
251+
return {ref, path};
252+
}
253+
152254
async function downloadCrystalNightly(suffix, destination = null) {
153255
Core.info("Looking for latest Crystal build");
154256

@@ -166,18 +268,16 @@ async function downloadCrystalNightly(suffix, destination = null) {
166268
throw "Could not find a matching nightly build";
167269
}
168270
}
169-
Core.info("Found " + build["build_url"]);
271+
Core.info(`Found Crystal build ${build["build_url"]}`);
170272
Core.setOutput("crystal", build["vcs_revision"]);
171273

172274
const req = `/${build["build_num"]}/artifacts`;
173275
const resp = await fetch(CircleApiBase + req);
174276
const artifacts = await resp.json();
175277
const artifact = artifacts.find((a) => a["path"].endsWith(`-${suffix}.tar.gz`));
176-
Core.info("Found " + artifact["url"]);
177278

178-
Core.info("Downloading Crystal build");
279+
Core.info(`Downloading Crystal build from ${artifact["url"]}`);
179280
const downloadedPath = await ToolCache.downloadTool(artifact["url"]);
180-
181281
Core.info("Extracting Crystal build");
182282
return onlySubdir(await ToolCache.extractTar(downloadedPath, destination));
183283
}
@@ -190,11 +290,8 @@ async function installCrystalForWindows({
190290
checkVersion(crystal, [Nightly]);
191291
checkArch(arch, ["x86_64"]);
192292
const path = await downloadCrystalNightlyForWindows(destination);
193-
await setupCrystalForWindows(path);
194-
}
195293

196-
async function setupCrystalForWindows(path) {
197-
Core.info("Setting up environment");
294+
Core.info("Setting up environment for Crystal");
198295
const vars = await variablesForVCBuildTools();
199296
addPathToVars(vars, "PATH", path);
200297
addPathToVars(vars, "LIB", path);
@@ -253,25 +350,9 @@ async function downloadCrystalNightlyForWindows(destination = null) {
253350
"per_page": 1,
254351
});
255352
const [workflowRun] = runsResp.data["workflow_runs"];
256-
const {"head_sha": ref, "id": runId} = workflowRun;
257-
Core.info("Found " + workflowRun["html_url"]);
258-
Core.setOutput("crystal", ref);
259-
260-
const fetchSrcTask = async (destDir) => {
261-
const zipballLinkResp = await githubGet({
262-
url: GitHubApiBase + "/zipball/:ref",
263-
"ref": ref,
264-
request: {redirect: "manual"},
265-
});
266-
const downloadUrl = zipballLinkResp.headers["location"];
267-
Core.info("Found " + downloadUrl);
268-
269-
Core.info("Downloading Crystal source");
270-
const downloadedPath = await ToolCache.downloadTool(downloadUrl);
271-
272-
Core.info("Extracting Crystal source");
273-
return onlySubdir(await ToolCache.extractZip(downloadedPath, destDir));
274-
};
353+
const {"head_sha": version, "id": runId} = workflowRun;
354+
Core.info(`Found Crystal release ${workflowRun["html_url"]}`);
355+
Core.setOutput("crystal", version);
275356

276357
const fetchExeTask = async () => {
277358
const artifactsResp = await githubGet({
@@ -291,8 +372,8 @@ async function downloadCrystalNightlyForWindows(destination = null) {
291372
return ToolCache.downloadTool(downloadUrl);
292373
};
293374

294-
const [srcPath, exeDownloadedPath] = await Promise.all([
295-
fetchSrcTask(destination),
375+
const [{path: srcPath}, exeDownloadedPath] = await Promise.all([
376+
downloadSource({name: "Crystal", apiBase: GitHubApiBase, version, destination}),
296377
fetchExeTask(),
297378
]);
298379

0 commit comments

Comments
 (0)