Skip to content

Commit 16d3b7b

Browse files
Implement first version of task to switch Xcode versions (#1)
* Implement version 1.0
1 parent 9e20a6c commit 16d3b7b

16 files changed

Lines changed: 8629 additions & 1 deletion

.eslintrc.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"env": {
3+
"node": true,
4+
"es6": true,
5+
"jest/globals": true
6+
},
7+
"extends": [
8+
"eslint:recommended",
9+
"plugin:@typescript-eslint/eslint-recommended",
10+
"plugin:@typescript-eslint/recommended",
11+
"plugin:jest/recommended"
12+
],
13+
"parser": "@typescript-eslint/parser",
14+
"parserOptions": {
15+
"project": "./tsconfig.eslint.json",
16+
"ecmaVersion": 2018,
17+
"sourceType": "module"
18+
},
19+
"plugins": ["@typescript-eslint", "jest"],
20+
"ignorePatterns": ["node_modules/"],
21+
"rules": {
22+
"indent": ["error", 4],
23+
"linebreak-style": ["error", "unix"],
24+
"quotes": ["error", "double"],
25+
"semi": ["error", "always"]
26+
}
27+
}

.github/workflows/test.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Validate 'setup-xcode'
2+
on:
3+
pull_request:
4+
schedule:
5+
- cron: 0 0 * * *
6+
7+
jobs:
8+
xcode-versions:
9+
name: xcode versions
10+
runs-on: macos-latest
11+
strategy:
12+
matrix:
13+
xcode-version: [10.3, 11, 11.2, 11.4.0, 11.4.1, ^11.4.0, latest]
14+
fail-fast: false
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v2
18+
19+
- name: setup-xcode
20+
uses: ./
21+
with:
22+
xcode-version: ${{ matrix.xcode-version }}

.github/workflows/workflow.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Build task
2+
on: [pull_request]
3+
4+
jobs:
5+
Build:
6+
runs-on: macos-latest
7+
steps:
8+
- name: Checkout
9+
uses: actions/checkout@master
10+
11+
- name: Set Node.JS
12+
uses: actions/setup-node@master
13+
with:
14+
node-version: 12.x
15+
16+
- name: npm install
17+
run: npm install
18+
19+
- name: Build
20+
run: npm run build
21+
22+
- name: Run tests
23+
run: npm run test
24+
25+
- name: Lint
26+
run: npm run lint

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
lib

LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
The MIT License (MIT)
3+
4+
Copyright (c) 2020 Maxim Lobanov and contributors
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in
14+
all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
THE SOFTWARE.

README.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,33 @@
11
# setup-xcode
2-
Set up your GitHub Actions workflow with a specific version of Xcode
2+
This action is intended to switch between pre-installed versions of Xcode for macOS images in GitHub Actions.
3+
4+
The list of all available versions can be found in [virtual-environments](https://github.com/actions/virtual-environments/blob/master/images/macos/macos-10.15-Readme.md#xcode) repository.
5+
6+
# Available parameters
7+
| Argument | Description | Format |
8+
|-------------------------|--------------------------|--------------------|
9+
| `xcode-version` | Specify the Xcode version to use | `latest` keyword or any [semver](https://semver.org/) string |
10+
**Examples:** `latest`, `10`, `11.4`, `11.4.0`, `^11.4.0`
11+
12+
# Usage
13+
```
14+
name: CI
15+
on: [push]
16+
jobs:
17+
build:
18+
name: Set
19+
runs-on: macos-latest
20+
steps:
21+
- name: setup-xcode
22+
uses: maxim-lobanov/setup-xcode@v1.0
23+
with:
24+
xcode-version: 11.4 # set the latest available Xcode 11.4.*
25+
26+
- name: setup-latest-xcode
27+
uses: maxim-lobanov/setup-xcode@v1.0
28+
with:
29+
xcode-version: latest # set the latest available Xcode 11.4.*
30+
```
31+
32+
# License
33+
The scripts and documentation in this project are released under the [MIT License](LICENSE)

__tests__/xcode-selector.test.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import * as fs from "fs";
2+
import * as child from "child_process";
3+
import * as core from "@actions/core";
4+
import { XcodeSelector, XcodeVersion } from "../src/xcode-selector";
5+
6+
jest.mock("fs");
7+
jest.mock("child_process");
8+
jest.mock("@actions/core");
9+
10+
const buildFsDirentItem = (name: string, opt: { isSymbolicLink: boolean; isDirectory: boolean }): fs.Dirent => {
11+
return {
12+
name,
13+
isSymbolicLink: () => opt.isSymbolicLink,
14+
isDirectory: () => opt.isDirectory
15+
} as fs.Dirent;
16+
};
17+
18+
const fakeReadDirResults = [
19+
buildFsDirentItem("Xcode.app", { isSymbolicLink: true, isDirectory: false }),
20+
buildFsDirentItem("Xcode.app", { isSymbolicLink: false, isDirectory: true }),
21+
buildFsDirentItem("Xcode_11.1.app", { isSymbolicLink: false, isDirectory: true }),
22+
buildFsDirentItem("Xcode_11.1_beta.app", { isSymbolicLink: true, isDirectory: false }),
23+
buildFsDirentItem("Xcode_11.2.1.app", { isSymbolicLink: false, isDirectory: true }),
24+
buildFsDirentItem("Xcode_11.4.app", { isSymbolicLink: true, isDirectory: false }),
25+
buildFsDirentItem("Xcode_11.4_beta.app", { isSymbolicLink: false, isDirectory: true }),
26+
buildFsDirentItem("Xcode_11.app", { isSymbolicLink: false, isDirectory: true }),
27+
buildFsDirentItem("third_party_folder", { isSymbolicLink: false, isDirectory: true }),
28+
];
29+
30+
const fakeGetVersionsResult: XcodeVersion[] = [
31+
{ version: "11.4.0", path: "" },
32+
{ version: "11.2.1", path: "" },
33+
{ version: "11.2.0", path: "" },
34+
{ version: "11.0.0", path: "" },
35+
{ version: "10.3.0", path: "" }
36+
];
37+
38+
describe("XcodeSelector", () => {
39+
describe("getXcodeVersionFromAppPath", () => {
40+
it.each([
41+
["/temp/Xcode_11.app", { version: "11.0.0", path: "/temp/Xcode_11.app"}],
42+
["/temp/Xcode_11.2.app", { version: "11.2.0", path: "/temp/Xcode_11.2.app"}],
43+
["/temp/Xcode_11.2.1.app", { version: "11.2.1", path: "/temp/Xcode_11.2.1.app"}],
44+
["/temp/Xcode_11.2.1_beta.app", { version: "11.2.1", path: "/temp/Xcode_11.2.1_beta.app"}],
45+
["/temp/Xcode.app", null],
46+
["/temp/Xcode_11.2", null],
47+
["/temp/Xcode.11.2.app", null]
48+
])("'%s' -> '%s'", (input: string, expected: XcodeVersion | null) => {
49+
// test private method
50+
const actual = new XcodeSelector()["getXcodeVersionFromAppPath"](input);
51+
expect(actual).toEqual(expected);
52+
});
53+
54+
});
55+
56+
describe("getAllVersions", () => {
57+
beforeEach(() => {
58+
jest.spyOn(fs, "readdirSync").mockImplementation(() => fakeReadDirResults);
59+
});
60+
61+
afterEach(() => {
62+
jest.resetAllMocks();
63+
jest.clearAllMocks();
64+
});
65+
66+
it("versions are filtered correctly", () => {
67+
const sel = new XcodeSelector();
68+
const expectedVersions: XcodeVersion[] = [
69+
{ version: "11.4.0", path: "/Applications/Xcode_11.4_beta.app" },
70+
{ version: "11.2.1", path: "/Applications/Xcode_11.2.1.app" },
71+
{ version: "11.1.0", path: "/Applications/Xcode_11.1.app" },
72+
{ version: "11.0.0", path: "/Applications/Xcode_11.app" }
73+
];
74+
expect(sel.getAllVersions()).toEqual(expectedVersions);
75+
});
76+
});
77+
78+
describe("findVersion", () => {
79+
it.each([
80+
["latest", "11.4.0"],
81+
["11", "11.4.0"],
82+
["11.x", "11.4.0"],
83+
["11.2.x", "11.2.1"],
84+
["11.2.0", "11.2.0"],
85+
["10.x", "10.3.0"],
86+
["~11.2.0", "11.2.1"],
87+
["^11.2.0", "11.4.0"],
88+
["< 11.0", "10.3.0"],
89+
["10.0.0 - 11.2.0", "11.2.0"],
90+
["give me latest version", null]
91+
] as [string, string | null][])("'%s' -> '%s'", (versionSpec: string, expected: string | null) => {
92+
const sel = new XcodeSelector();
93+
sel.getAllVersions = (): XcodeVersion[] => fakeGetVersionsResult;
94+
const matchedVersion = sel.findVersion(versionSpec)?.version ?? null;
95+
expect(matchedVersion).toBe(expected);
96+
});
97+
});
98+
99+
describe("setVersion", () => {
100+
let coreExportVariableSpy: jest.SpyInstance;
101+
let fsExistsSpy: jest.SpyInstance;
102+
let fsSpawnSpy: jest.SpyInstance;
103+
const xcodeVersion: XcodeVersion = {
104+
version: "11.4",
105+
path: "/Applications/Xcode_11.4.app"
106+
};
107+
108+
beforeEach(() => {
109+
coreExportVariableSpy = jest.spyOn(core, "exportVariable");
110+
fsExistsSpy = jest.spyOn(fs, "existsSync");
111+
fsSpawnSpy = jest.spyOn(child, "spawnSync");
112+
});
113+
114+
afterEach(() => {
115+
jest.resetAllMocks();
116+
jest.clearAllMocks();
117+
});
118+
119+
it("works correctly", () => {
120+
fsExistsSpy.mockImplementation(() => true);
121+
const sel = new XcodeSelector();
122+
sel.setVersion(xcodeVersion);
123+
expect(fsSpawnSpy).toHaveBeenCalledWith("sudo", ["xcode-select", "-s", "/Applications/Xcode_11.4.app"]);
124+
expect(coreExportVariableSpy).toHaveBeenCalledWith("MD_APPLE_SDK_ROOT", "/Applications/Xcode_11.4.app");
125+
});
126+
127+
it("error is thrown if version doesn't exist", () => {
128+
fsExistsSpy.mockImplementation(() => false);
129+
const sel = new XcodeSelector();
130+
expect(() => sel.setVersion(xcodeVersion)).toThrow();
131+
});
132+
});
133+
});

action.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: 'Setup Xcode'
2+
author: 'Maxim Lobanov'
3+
description: 'Set up your GitHub Actions workflow with a specific version of Xcode'
4+
inputs:
5+
xcode-version:
6+
description: 'Version of Xcode to use'
7+
required: false
8+
default: latest
9+
runs:
10+
using: 'node12'
11+
main: 'dist/index.js'
12+
branding:
13+
icon: 'code'
14+
color: 'blue'

0 commit comments

Comments
 (0)