├── tsconfig.eslint.json ├── src ├── tool-selector.ts ├── xamarin-ios-selector.ts ├── xamarin-mac-selector.ts ├── xamarin-android-selector.ts ├── utils.ts ├── version-utils.ts ├── mono-selector.ts ├── xamarin-selector.ts ├── setup-xamarin.ts └── xcode-selector.ts ├── jest.config.js ├── __tests__ ├── validate-xcode-version.ps1 ├── validate-xamarin-versions.ps1 ├── version-utils.test.ts ├── xcode-selector.test.ts └── xamarin-selector.test.ts ├── tsconfig.json ├── .github └── workflows │ ├── workflow.yml │ └── test.yml ├── action.yml ├── .eslintrc.json ├── LICENSE ├── package.json ├── .gitignore ├── README.md └── dist └── index.js /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["**/*.ts"], 4 | "exclude": [] 5 | } -------------------------------------------------------------------------------- /src/tool-selector.ts: -------------------------------------------------------------------------------- 1 | export interface ToolSelector { 2 | readonly toolName: string; 3 | getAllVersions(): string[]; 4 | findVersion(versionSpec: string): string | null; 5 | setVersion(version: string): void; 6 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | testEnvironment: 'node', 5 | testMatch: ['**/*.test.ts'], 6 | testRunner: 'jest-circus/runner', 7 | transform: { 8 | '^.+\\.ts$': 'ts-jest' 9 | }, 10 | verbose: true 11 | } -------------------------------------------------------------------------------- /src/xamarin-ios-selector.ts: -------------------------------------------------------------------------------- 1 | import { XamarinSelector } from "./xamarin-selector"; 2 | 3 | export class XamarinIosToolSelector extends XamarinSelector { 4 | public get toolName(): string { 5 | return "Xamarin.iOS"; 6 | } 7 | 8 | protected get basePath(): string { 9 | return "/Library/Frameworks/Xamarin.iOS.framework"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/xamarin-mac-selector.ts: -------------------------------------------------------------------------------- 1 | import { XamarinSelector } from "./xamarin-selector"; 2 | 3 | export class XamarinMacToolSelector extends XamarinSelector { 4 | public get toolName(): string { 5 | return "Xamarin.Mac"; 6 | } 7 | 8 | protected get basePath(): string { 9 | return "/Library/Frameworks/Xamarin.Mac.framework"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/xamarin-android-selector.ts: -------------------------------------------------------------------------------- 1 | import { XamarinSelector } from "./xamarin-selector"; 2 | 3 | export class XamarinAndroidToolSelector extends XamarinSelector { 4 | public get toolName(): string { 5 | return "Xamarin.Android"; 6 | } 7 | 8 | protected get basePath(): string { 9 | return "/Library/Frameworks/Xamarin.Android.framework"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /__tests__/validate-xcode-version.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$XcodeVersion 3 | ) 4 | 5 | $expectedXcodePath = "/Applications/Xcode_$XcodeVersion.app" 6 | 7 | Write-Host "Check Xcode version" 8 | $actualXcodePath = & xcode-select -p 9 | if (!$actualXcodePath.StartsWith($expectedXcodePath)) { 10 | Write-Error "Incorrect Xcode: $actualXcodePath" 11 | exit 1 12 | } 13 | 14 | if ($env:MD_APPLE_SDK_ROOT -ne $expectedXcodePath) { 15 | Write-Error "Incorrect Xcode: $($env:MD_APPLE_SDK_ROOT)" 16 | exit 1 17 | } 18 | 19 | Write-Host "Correct Xcode: $XcodeVersion" -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "commonjs", 5 | "outDir": "./lib", 6 | "rootDir": "./src", 7 | "esModuleInterop": true, 8 | 9 | /* Strict Type-Checking Options */ 10 | "strict": true, 11 | "noImplicitAny": true, 12 | "strictNullChecks": true, 13 | "strictFunctionTypes": true, 14 | 15 | /* Additional Checks */ 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true 19 | }, 20 | "include": ["./src/**/*.ts"], 21 | "exclude": ["node_modules", "./__tests__/**/*.ts"] 22 | } -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Build task 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: 0 0 * * * 10 | 11 | jobs: 12 | Build: 13 | runs-on: macos-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@master 17 | 18 | - name: Set Node.JS 19 | uses: actions/setup-node@master 20 | with: 21 | node-version: 12.x 22 | 23 | - name: npm install 24 | run: npm install 25 | 26 | - name: Build 27 | run: npm run build 28 | 29 | - name: Run tests 30 | run: npm run test 31 | 32 | - name: Lint 33 | run: npm run lint 34 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Setup Xamarin' 2 | author: 'Maxim Lobanov' 3 | description: 'Set up your GitHub Actions workflow with a specific version of Xamarin & Mono' 4 | inputs: 5 | mono-version: 6 | description: 'Version of Mono to select' 7 | required: false 8 | xamarin-ios-version: 9 | description: 'Version of Xamarin.iOS to select' 10 | required: false 11 | xamarin-mac-version: 12 | description: 'Version of Xamarin.Mac to select' 13 | required: false 14 | xamarin-android-version: 15 | description: 'Version of Xamarin.Android to select' 16 | required: false 17 | xcode-version: 18 | description: 'Version of Xcode to use with Xamarin.iOS and Xamarin.Mac' 19 | required: false 20 | runs: 21 | using: 'node12' 22 | main: 'dist/index.js' 23 | branding: 24 | icon: 'code' 25 | color: 'yellow' -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 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 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as child from "child_process"; 2 | import { EOL } from "os"; 3 | 4 | interface InvokeCommandOptions { 5 | sudo: boolean; 6 | } 7 | 8 | export const invokeCommandSync = (command: string, args: string[], options: InvokeCommandOptions): void => { 9 | let execResult: child.SpawnSyncReturns; 10 | 11 | if (options.sudo) { 12 | execResult = child.spawnSync("sudo", [command, ...args]); 13 | } else { 14 | execResult = child.spawnSync(command, args); 15 | } 16 | 17 | if (execResult.status !== 0) { 18 | const fullCommand = `${options.sudo ? "sudo " : ""}${command} ${args.join(" ")}`; 19 | throw new Error( 20 | [ 21 | `Error during run '${fullCommand}'`, 22 | execResult.stderr, 23 | execResult.stdout 24 | ].join(EOL) 25 | ); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "setup-xamarin", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Set up your GitHub Actions workflow with a specific version of Mono & Xamarin", 6 | "main": "lib/setup-xamarin.js", 7 | "scripts": { 8 | "build": "tsc && ncc build", 9 | "test": "jest", 10 | "lint": "npx eslint **/*.ts", 11 | "pre-commit": "npm run build && npm run test && npm run lint" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/maxim-lobanov/setup-xamarin.git" 16 | }, 17 | "keywords": [ 18 | "actions", 19 | "xamarin", 20 | "mono", 21 | "setup" 22 | ], 23 | "author": "Maxim Lobanov", 24 | "license": "MIT", 25 | "dependencies": { 26 | "@actions/core": "^1.2.6", 27 | "compare-versions": "^3.6.0" 28 | }, 29 | "devDependencies": { 30 | "@types/jest": "^26.0.14", 31 | "@types/node": "^12.0.4", 32 | "@typescript-eslint/eslint-plugin": "^4.4.0", 33 | "@typescript-eslint/parser": "^4.4.0", 34 | "@zeit/ncc": "^0.22.3", 35 | "eslint": "7.11.0", 36 | "eslint-plugin-jest": "^24.1.0", 37 | "jest": "^26.5.2", 38 | "jest-circus": "^26.5.2", 39 | "ts-jest": "26.4.1", 40 | "typescript": "^4.0.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /__tests__/validate-xamarin-versions.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$MonoVersion, 3 | [string]$XamarinIOSVersion, 4 | [string]$XamarinMacVersion, 5 | [string]$XamarinAndroidVersion 6 | ) 7 | 8 | function Test-ToolVersion { 9 | param ( 10 | [string]$ToolName, 11 | [string]$ExpectedVersion 12 | ) 13 | 14 | if ([string]::IsNullOrEmpty($ExpectedVersion)) { 15 | return 16 | } 17 | 18 | Write-Host "Check $ToolName Version..." 19 | 20 | $versionFilePath = "/Library/Frameworks/$ToolName.framework/Versions/Current/Version" 21 | $actualVersion = Get-Content $versionFilePath 22 | if (!$actualVersion.StartsWith($ExpectedVersion)) { 23 | Write-Error("Incorrect $ToolName version: $actualVersion") 24 | exit 1 25 | } 26 | 27 | Write-Host "Correct $ToolName version: $ExpectedVersion" 28 | } 29 | 30 | if (![string]::IsNullOrEmpty($MonoVersion)) { 31 | Write-Host "Check Mono Version..." 32 | $actualVersion = & mono --version 33 | if (!$actualVersion[0].StartsWith("Mono JIT compiler version $MonoVersion")) { 34 | Write-Error("Incorrect Mono version: $actualVersion") 35 | exit 1 36 | } 37 | } 38 | 39 | Test-ToolVersion -ToolName "Mono" -ExpectedVersion $MonoVersion 40 | Test-ToolVersion -ToolName "Xamarin.IOS" -ExpectedVersion $XamarinIOSVersion 41 | Test-ToolVersion -ToolName "Xamarin.Mac" -ExpectedVersion $XamarinMacVersion 42 | Test-ToolVersion -ToolName "Xamarin.Android" -ExpectedVersion $XamarinAndroidVersion -------------------------------------------------------------------------------- /src/version-utils.ts: -------------------------------------------------------------------------------- 1 | import compareVersions from "compare-versions"; 2 | 3 | export class VersionUtils { 4 | public static isValidVersion = (version: string): boolean => { 5 | return compareVersions.validate(version); 6 | } 7 | 8 | public static isLatestVersionKeyword = (version: string): boolean => { 9 | return version === "latest"; 10 | } 11 | 12 | public static isVersionsEqual = (firstVersion: string, secondVersion: string): boolean => { 13 | return compareVersions.compare(firstVersion, secondVersion, "="); 14 | } 15 | 16 | public static sortVersions = (versions: string[]): string[] => { 17 | return [...versions].sort(compareVersions).reverse(); 18 | } 19 | 20 | public static normalizeVersion = (version: string): string => { 21 | const versionParts = VersionUtils.splitVersionToParts(version); 22 | while (versionParts.length < 4) { 23 | versionParts.push("x"); 24 | } 25 | 26 | return VersionUtils.buildVersionFromParts(versionParts); 27 | } 28 | 29 | public static countVersionLength = (version: string): number => { 30 | return VersionUtils.splitVersionToParts(version).length; 31 | } 32 | 33 | public static cutVersionLength = (version: string, newLength: number): string => { 34 | const versionParts = VersionUtils.splitVersionToParts(version); 35 | const newParts = versionParts.slice(0, newLength); 36 | return VersionUtils.buildVersionFromParts(newParts); 37 | } 38 | 39 | private static splitVersionToParts = (version: string): string[] => { 40 | return version.split("."); 41 | } 42 | 43 | private static buildVersionFromParts = (versionParts: string[]): string => { 44 | return versionParts.join("."); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib 3 | 4 | # Rest of the file pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # next.js build output 76 | .next 77 | 78 | # nuxt.js build output 79 | .nuxt 80 | 81 | # vuepress build output 82 | .vuepress/dist 83 | 84 | # Serverless directories 85 | .serverless/ 86 | 87 | # FuseBox cache 88 | .fusebox/ 89 | 90 | # DynamoDB Local files 91 | .dynamodb/ -------------------------------------------------------------------------------- /src/mono-selector.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import * as core from "@actions/core"; 4 | import { XamarinSelector } from "./xamarin-selector"; 5 | import { VersionUtils } from "./version-utils"; 6 | 7 | export class MonoToolSelector extends XamarinSelector { 8 | protected get basePath(): string { 9 | return "/Library/Frameworks/Mono.framework"; 10 | } 11 | 12 | public get toolName(): string { 13 | return "Mono"; 14 | } 15 | 16 | public getAllVersions(): string[] { 17 | const versionsFolders = super.getAllVersions(); 18 | 19 | // we have to look into '/Mono.Framework/Versions//Version' file for Mono to determine full version with 4 digits 20 | return versionsFolders.map(version => { 21 | const versionFile = path.join(this.versionsDirectoryPath, version, "Version"); 22 | const realVersion = fs.readFileSync(versionFile).toString(); 23 | return realVersion.trim(); 24 | }); 25 | } 26 | 27 | public setVersion(version: string): void { 28 | // for Mono, version folder contains only 3 digits instead of full version 29 | version = VersionUtils.cutVersionLength(version, 3); 30 | 31 | super.setVersion(version); 32 | 33 | const versionDirectory = this.getVersionPath(version); 34 | core.exportVariable( 35 | "DYLD_LIBRARY_FALLBACK_PATH", 36 | [ 37 | `${versionDirectory}/lib`, 38 | "/lib", 39 | "/usr/lib", 40 | process.env["DYLD_LIBRARY_FALLBACK_PATH"] 41 | ].join(path.delimiter) 42 | ); 43 | core.exportVariable( 44 | "PKG_CONFIG_PATH", 45 | [ 46 | `${versionDirectory}/lib/pkgconfig`, 47 | process.env["PKG_CONFIG_PATH"] 48 | ].join(path.delimiter) 49 | ); 50 | 51 | core.debug(`Add '${versionDirectory}/bin' to PATH`); 52 | core.addPath(`${versionDirectory}/bin`); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Validate 'setup-xamarin' 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: 0 0 * * * 10 | 11 | jobs: 12 | xamarin-partial-versions: 13 | name: xamarin - valid versions 14 | runs-on: macos-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | 19 | - name: setup-xamarin 20 | uses: ./ 21 | with: 22 | mono-version: '6.6' 23 | xamarin-ios-version: '14.0' 24 | xamarin-mac-version: '6.6' 25 | xamarin-android-version: '10.1' 26 | 27 | - name: Validate versions 28 | run: pwsh ./__tests__/validate-xamarin-versions.ps1 "6.6" "14.0" "6.6" "10.1" 29 | 30 | xamarin-latest-keyword: 31 | name: xamarin - latest keyword 32 | runs-on: macos-latest 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v2 36 | 37 | - name: setup-xamarin 38 | uses: ./ 39 | with: 40 | mono-version: latest 41 | xamarin-ios-version: latest 42 | xamarin-mac-version: latest 43 | xamarin-android-version: latest 44 | 45 | xamarin-full-versions: 46 | name: xamarin - valid full versions (should warn) 47 | runs-on: macos-latest 48 | steps: 49 | - name: Checkout 50 | uses: actions/checkout@v2 51 | 52 | - name: setup-xamarin 53 | uses: ./ 54 | with: 55 | xamarin-ios-version: 13.10.0.21 56 | xamarin-mac-version: 6.6.0.12 57 | 58 | - name: Validate versions 59 | run: pwsh ./__tests__/validate-xamarin-versions.ps1 -XamarinIOSVersion "13.10.0.21" -XamarinMacVersion "6.6.0.12" 60 | 61 | xcode-full-version: 62 | name: xcode - valid version 63 | runs-on: macos-latest 64 | steps: 65 | - name: Checkout 66 | uses: actions/checkout@v2 67 | 68 | - name: setup-xamarin 69 | uses: ./ 70 | with: 71 | xcode-version: 12.3 72 | 73 | - name: Validate versions 74 | run: pwsh ./__tests__/validate-xcode-version.ps1 -XcodeVersion "12.3" 75 | 76 | xcode-wildcard-version: 77 | name: xcode - wildcard version 78 | runs-on: macos-latest 79 | steps: 80 | - name: Checkout 81 | uses: actions/checkout@v2 82 | 83 | - name: setup-xamarin 84 | uses: ./ 85 | with: 86 | xcode-version: 11.x -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # setup-xamarin 2 | This action is intended to switch between pre-installed versions of Xamarin and Mono for macOS images in GitHub Actions. 3 | 4 | # Available parameters 5 | | Argument | Required | Description | Available versions | 6 | |-------------------------|----------|-----------------------------------------------------------|--------------------| 7 | | mono-version | False | Specify the version of Mono to switch | [Link](https://github.com/actions/virtual-environments/blob/master/images/macos/macos-10.15-Readme.md#mono) | 8 | | xamarin-ios-version | False | Specify the version of Xamarin.iOS to switch | [Link](https://github.com/actions/virtual-environments/blob/master/images/macos/macos-10.15-Readme.md#xamarinios) | 9 | | xamarin-mac-version | False | Specify the version of Xamarin.Mac to switch | [Link](https://github.com/actions/virtual-environments/blob/master/images/macos/macos-10.15-Readme.md#xamarinmac) | 10 | | xamarin-android-version | False | Specify the version of Xamarin.Android to switch | [Link](https://github.com/actions/virtual-environments/blob/master/images/macos/macos-10.15-Readme.md#xamarinandroid) | 11 | | xcode-version | False | Specify the Xcode to use with Xamarin.iOS and Xamarin.Mac | [Link](https://github.com/actions/virtual-environments/blob/master/images/macos/macos-10.15-Readme.md#xcode) | 12 | 13 | - `mono-version`, `xamarin-ios-version`, `xamarin-mac-version`, `xamarin-android-version` parameters support the following format: `latest`, `13`, `13.2`, `13.2.1.4` 14 | - `xcode-version` parameter supports the following format: `latest`, `11.4`, `11.x`, `11.2.1` 15 | **Note:** If you need to switch Xcode only without Xamarin - please consider using [maxim-lobanov/setup-xcode](https://github.com/maxim-lobanov/setup-xcode) actions since it provides more comfortable way to specify Xcode. 16 | 17 | # Usage 18 | ``` 19 | name: CI 20 | on: [push] 21 | jobs: 22 | build: 23 | name: Setup Xamarin and Mono versions 24 | runs-on: macos-latest 25 | steps: 26 | - name: setup-xamarin 27 | uses: maxim-lobanov/setup-xamarin@v1 28 | with: 29 | mono-version: '6.6' # specify version in '.' format 30 | xamarin-ios-version: '13' # specify version in '' format 31 | xamarin-mac-version: latest # specify 'latest' keyword to pick up the latest available version 32 | xamarin-android-version: '10.1.3.7' # specify full version; it is not recomended option because your pipeline can be broken suddenly in future 33 | xcode-version: '11.x' # set the latest available Xcode 11 34 | ``` 35 | 36 | # License 37 | The scripts and documentation in this project are released under the [MIT License](LICENSE) 38 | -------------------------------------------------------------------------------- /__tests__/version-utils.test.ts: -------------------------------------------------------------------------------- 1 | import { VersionUtils } from "../src/version-utils"; 2 | 3 | describe("VersionUtils", () => { 4 | describe("validVersion", () => { 5 | it.each([ 6 | ["5", true], 7 | ["5.2", true], 8 | ["5.2.3", true], 9 | ["5.2.3.1", true], 10 | ["5.2.3.1.6", false], 11 | ["invalid_version_format", false], 12 | ["", false] 13 | ])("'%s' -> %s", (version: string, expected: boolean) => { 14 | expect(VersionUtils.isValidVersion(version)).toBe(expected); 15 | }); 16 | }); 17 | 18 | it("sortVersions", () => { 19 | const actual = VersionUtils.sortVersions([ 20 | "11.2", 21 | "11.4", 22 | "10.1", 23 | "11.2.1", 24 | "10.2" 25 | ]); 26 | expect(actual).toEqual([ 27 | "11.4", 28 | "11.2.1", 29 | "11.2", 30 | "10.2", 31 | "10.1" 32 | ]); 33 | }); 34 | 35 | describe("isVersionsEqual", () => { 36 | it.each([ 37 | ["11.2", "11.2", true], 38 | ["11.x", "11.2", true], 39 | ["11.x.x", "11.2", true], 40 | ["11.x.x", "11.2.1", true], 41 | ["11", "11.2", false], 42 | ["11", "11.2.1", false], 43 | ["10", "11.2", false] 44 | ])("'%s', '%s' -> %s", (firstVersion: string, secondVersion: string, expected: boolean) => { 45 | const actual = VersionUtils.isVersionsEqual(firstVersion, secondVersion); 46 | expect(actual).toBe(expected); 47 | }); 48 | }); 49 | 50 | describe("normalizeVersion", () => { 51 | it.each([ 52 | ["5", "5.x.x.x"], 53 | ["5.2", "5.2.x.x"], 54 | ["5.2.3", "5.2.3.x"], 55 | ["5.2.3.1", "5.2.3.1"] 56 | ])("'%s' -> '%s'", (version: string, expected: string) => { 57 | expect(VersionUtils.normalizeVersion(version)).toBe(expected); 58 | }); 59 | }); 60 | 61 | describe("countVersionLength", () => { 62 | it.each([ 63 | ["5", 1], 64 | ["5.2", 2], 65 | ["5.2.3", 3], 66 | ["5.2.3.1", 4] 67 | ])("'%s' -> %d", (version: string, expected: number) => { 68 | expect(VersionUtils.countVersionLength(version)).toBe(expected); 69 | }); 70 | }); 71 | 72 | describe("cutVersionLength", () => { 73 | it.each([ 74 | ["5.2.3.1", 4, "5.2.3.1"], 75 | ["5.2.3.1", 3, "5.2.3"], 76 | ["5.2.3.1", 2, "5.2"], 77 | ["5.2.3.1", 1, "5"] 78 | ])("'%s', %d -> '%s'", (version: string, newLength: number, expected: string) => { 79 | expect(VersionUtils.cutVersionLength(version, newLength)).toBe(expected); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /src/xamarin-selector.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import * as core from "@actions/core"; 4 | import * as utils from "./utils"; 5 | import { VersionUtils } from "./version-utils"; 6 | import { ToolSelector } from "./tool-selector"; 7 | 8 | export abstract class XamarinSelector implements ToolSelector { 9 | public abstract get toolName(): string; 10 | 11 | protected abstract get basePath(): string; 12 | 13 | protected get versionsDirectoryPath(): string { 14 | return path.join(this.basePath, "Versions"); 15 | } 16 | 17 | protected getVersionPath(version: string): string { 18 | return path.join(this.versionsDirectoryPath, version); 19 | } 20 | 21 | public getAllVersions(): string[] { 22 | const children = fs.readdirSync(this.versionsDirectoryPath, { encoding: "utf8", withFileTypes: true }); 23 | 24 | // macOS image contains symlinks for full versions, like '13.2' -> '13.2.3.0' 25 | // filter such symlinks and look for only real versions 26 | let potentialVersions = children.filter(child => !child.isSymbolicLink() && child.isDirectory()).map(child => child.name); 27 | potentialVersions = potentialVersions.filter(child => VersionUtils.isValidVersion(child)); 28 | 29 | // sort versions array by descending to make sure that the newest version will be picked up 30 | return VersionUtils.sortVersions(potentialVersions); 31 | } 32 | 33 | public findVersion(versionSpec: string): string | null { 34 | const availableVersions = this.getAllVersions(); 35 | if (availableVersions.length === 0) { 36 | return null; 37 | } 38 | 39 | if (VersionUtils.isLatestVersionKeyword(versionSpec)) { 40 | return availableVersions[0]; 41 | } 42 | 43 | const normalizedVersionSpec = VersionUtils.normalizeVersion(versionSpec); 44 | core.debug(`Semantic version spec of '${versionSpec}' is '${normalizedVersionSpec}'`); 45 | 46 | return availableVersions.find(ver => VersionUtils.isVersionsEqual(ver, normalizedVersionSpec)) ?? null; 47 | } 48 | 49 | public setVersion(version: string): void { 50 | const targetVersionDirectory = this.getVersionPath(version); 51 | if (!fs.existsSync(targetVersionDirectory)) { 52 | throw new Error(`Invalid version: Directory '${targetVersionDirectory}' doesn't exist`); 53 | } 54 | 55 | const currentVersionDirectory = path.join(this.versionsDirectoryPath, "Current"); 56 | core.debug(`Creating symlink '${currentVersionDirectory}' -> '${targetVersionDirectory}'`); 57 | if (fs.existsSync(currentVersionDirectory)) { 58 | utils.invokeCommandSync("rm", ["-f", currentVersionDirectory], { sudo: true }); 59 | } 60 | 61 | utils.invokeCommandSync("ln", ["-s", targetVersionDirectory, currentVersionDirectory], { sudo: true }); 62 | } 63 | 64 | public static toString(): string { 65 | // show correct name for test suite 66 | return this.name; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/setup-xamarin.ts: -------------------------------------------------------------------------------- 1 | import * as core from "@actions/core"; 2 | import { MonoToolSelector } from "./mono-selector"; 3 | import { XamarinIosToolSelector } from "./xamarin-ios-selector"; 4 | import { XamarinMacToolSelector } from "./xamarin-mac-selector"; 5 | import { XamarinAndroidToolSelector } from "./xamarin-android-selector"; 6 | import { EOL } from "os"; 7 | import { VersionUtils } from "./version-utils"; 8 | import { ToolSelector } from "./tool-selector"; 9 | import { XcodeSelector } from "./xcode-selector"; 10 | 11 | let showVersionMajorMinorWarning = false; 12 | 13 | const invokeSelector = (variableName: string, toolSelector: { new (): ToolSelector }): void => { 14 | const versionSpec = core.getInput(variableName, { required: false }); 15 | if (!versionSpec) { 16 | return; 17 | } 18 | 19 | const selector = new toolSelector(); 20 | 21 | if (!VersionUtils.isLatestVersionKeyword(versionSpec) && !VersionUtils.isValidVersion(versionSpec)) { 22 | throw new Error(`Value '${versionSpec}' is not valid version for ${selector.toolName}`); 23 | } 24 | 25 | core.info(`Switching ${selector.toolName} to version '${versionSpec}'...`); 26 | 27 | const targetVersion = selector.findVersion(versionSpec); 28 | if (!targetVersion) { 29 | throw new Error( 30 | [ 31 | `Could not find ${selector.toolName} version that satisfied version spec: ${versionSpec}`, 32 | "Available versions:", 33 | ...selector.getAllVersions().map(ver => `- ${ver}`) 34 | ].join(EOL) 35 | ); 36 | } 37 | 38 | core.debug(`${selector.toolName} ${targetVersion} will be set`); 39 | selector.setVersion(targetVersion); 40 | core.info(`${selector.toolName} is set to '${targetVersion}'`); 41 | 42 | showVersionMajorMinorWarning = showVersionMajorMinorWarning || VersionUtils.countVersionLength(versionSpec) > 2; 43 | }; 44 | 45 | const run = (): void => { 46 | try { 47 | if (process.platform !== "darwin") { 48 | throw new Error(`This task is intended only for macOS platform. It can't be run on '${process.platform}' platform`); 49 | } 50 | 51 | invokeSelector("mono-version", MonoToolSelector); 52 | invokeSelector("xamarin-ios-version", XamarinIosToolSelector); 53 | invokeSelector("xamarin-mac-version", XamarinMacToolSelector); 54 | invokeSelector("xamarin-android-version", XamarinAndroidToolSelector); 55 | invokeSelector("xcode-version", XcodeSelector); 56 | 57 | if (showVersionMajorMinorWarning) { 58 | core.warning( 59 | [ 60 | "It is recommended to specify only major and minor versions of tool (like '13' or '13.2').", 61 | "Hosted VMs contain the latest patch & build version for each major & minor pair.", 62 | "It means that version '13.2.1.4' can be replaced by '13.2.2.0' without any notice and your pipeline will start failing." 63 | ].join(" ") 64 | ); 65 | } 66 | } catch (error) { 67 | core.setFailed(error.message); 68 | } 69 | }; 70 | 71 | run(); 72 | -------------------------------------------------------------------------------- /src/xcode-selector.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import * as core from "@actions/core"; 4 | import { VersionUtils } from "./version-utils"; 5 | import { ToolSelector } from "./tool-selector"; 6 | import * as utils from "./utils"; 7 | 8 | export class XcodeSelector implements ToolSelector { 9 | private readonly xcodeDirectoryPath = "/Applications"; 10 | private readonly xcodeFilenameRegex = /Xcode_([\d.]+)(_beta)?\.app/; 11 | 12 | private parseXcodeVersionFromFilename(filename: string): string | null { 13 | const match = filename.match(this.xcodeFilenameRegex); 14 | if (!match || match.length < 2) { 15 | return null; 16 | } 17 | 18 | return match[1]; 19 | } 20 | 21 | public get toolName(): string { 22 | return "Xcode"; 23 | } 24 | 25 | protected getVersionPath(version: string): string { 26 | return path.join(this.xcodeDirectoryPath, `Xcode_${version}.app`); 27 | } 28 | 29 | public getAllVersions(): string[] { 30 | const children = fs.readdirSync(this.xcodeDirectoryPath, { encoding: "utf8", withFileTypes: true }); 31 | 32 | let potentialVersions = children.filter(child => !child.isSymbolicLink() && child.isDirectory()).map(child => child.name); 33 | potentialVersions = potentialVersions.map(child => this.parseXcodeVersionFromFilename(child)).filter((child): child is string => !!child); 34 | 35 | const stableVersions = potentialVersions.filter(ver => VersionUtils.isValidVersion(ver)); 36 | const betaVersions = potentialVersions.filter(ver => ver.endsWith("_beta")).map(ver => { 37 | const verWithoutBeta = ver.substr(0, ver.length - 5); 38 | return children.find(child => child.isSymbolicLink() && this.parseXcodeVersionFromFilename(child.name) === verWithoutBeta)?.name; 39 | }).filter(((ver): ver is string => !!ver && VersionUtils.isValidVersion(ver))); 40 | 41 | // sort versions array by descending to make sure that the newest version will be picked up 42 | return VersionUtils.sortVersions([...stableVersions, ...betaVersions]); 43 | } 44 | 45 | findVersion(versionSpec: string): string | null { 46 | const availableVersions = this.getAllVersions(); 47 | if (availableVersions.length === 0) { 48 | return null; 49 | } 50 | 51 | if (VersionUtils.isLatestVersionKeyword(versionSpec)) { 52 | return availableVersions[0]; 53 | } 54 | 55 | return availableVersions.find(ver => VersionUtils.isVersionsEqual(ver, versionSpec)) ?? null; 56 | } 57 | 58 | setVersion(version: string): void { 59 | const targetVersionDirectory = this.getVersionPath(version); 60 | if (!fs.existsSync(targetVersionDirectory)) { 61 | throw new Error(`Invalid version: Directory '${targetVersionDirectory}' doesn't exist`); 62 | } 63 | 64 | core.debug(`sudo xcode-select -s ${targetVersionDirectory}`); 65 | utils.invokeCommandSync("xcode-select", ["-s", targetVersionDirectory], { sudo: true }); 66 | 67 | core.exportVariable("MD_APPLE_SDK_ROOT", targetVersionDirectory); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /__tests__/xcode-selector.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as core from "@actions/core"; 3 | import * as utils from "../src/utils"; 4 | import { XcodeSelector } from "../src/xcode-selector"; 5 | import { VersionUtils } from "../src/version-utils"; 6 | 7 | jest.mock("fs"); 8 | jest.mock("@actions/core"); 9 | jest.mock("../src/utils"); 10 | 11 | const buildFsDirentItem = (name: string, opt: { isSymbolicLink: boolean; isDirectory: boolean }): fs.Dirent => { 12 | return { 13 | name, 14 | isSymbolicLink: () => opt.isSymbolicLink, 15 | isDirectory: () => opt.isDirectory 16 | } as fs.Dirent; 17 | }; 18 | 19 | const fakeReadDirResults = [ 20 | buildFsDirentItem("Xcode.app", { isSymbolicLink: true, isDirectory: false }), 21 | buildFsDirentItem("Xcode.app", { isSymbolicLink: false, isDirectory: true }), 22 | buildFsDirentItem("Xcode_11.1.app", { isSymbolicLink: false, isDirectory: true }), 23 | buildFsDirentItem("Xcode_11.1_beta.app", { isSymbolicLink: true, isDirectory: false }), 24 | buildFsDirentItem("Xcode_11.2.1.app", { isSymbolicLink: false, isDirectory: true }), 25 | buildFsDirentItem("Xcode_11.4.app", { isSymbolicLink: true, isDirectory: false }), 26 | buildFsDirentItem("Xcode_11.4_beta.app", { isSymbolicLink: false, isDirectory: true }), 27 | buildFsDirentItem("Xcode_11.app", { isSymbolicLink: false, isDirectory: true }), 28 | buildFsDirentItem("third_party_folder", { isSymbolicLink: false, isDirectory: true }), 29 | ]; 30 | 31 | const fakeGetVersionsResult = VersionUtils.sortVersions([ 32 | "10.3", 33 | "11", 34 | "11.2", 35 | "11.2.1", 36 | "11.4" 37 | ]); 38 | 39 | describe("XcodeSelector", () => { 40 | describe("xcodeRegex", () => { 41 | it.each([ 42 | ["Xcode_11.app", "11"], 43 | ["Xcode_11.2.app", "11.2"], 44 | ["Xcode_11.2.1.app", "11.2.1"], 45 | ["Xcode.app", null], 46 | ["Xcode_11.2", null], 47 | ["Xcode.11.2.app", null] 48 | ])("'%s' -> '%s'", (input: string, expected: string | null) => { 49 | // test private method 50 | const actual = new XcodeSelector()["parseXcodeVersionFromFilename"](input); 51 | expect(actual).toBe(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 = [ 69 | "11.4", 70 | "11.2.1", 71 | "11.1", 72 | "11" 73 | ]; 74 | expect(sel.getAllVersions()).toEqual(expectedVersions); 75 | }); 76 | }); 77 | 78 | describe("findVersion", () => { 79 | it.each([ 80 | ["latest", "11.4", "latest is matched"], 81 | ["11", "11", "one digit is matched"], 82 | ["11.x", "11.4", "one digit is matched and latest version is selected"], 83 | ["10", null, "one digit is not matched"], 84 | ["11.2", "11.2", "two digits are matched"], 85 | ["11.2.x", "11.2.1", "two digits are matched and latest version is selected"], 86 | ["11.4.x", "11.4", "the latest patch version is matched"], 87 | ["10.4", null, "two digits are not matched"], 88 | ["9.x", null, "two digits are not matched"], 89 | ["11.5.x", null, "three digits are not matched"], 90 | ["11.2.1", "11.2.1", "full version is matched"] 91 | ] as [string, string | null, string][])("'%s' -> '%s' (%s)", (versionSpec: string, expected: string | null) => { 92 | const sel = new XcodeSelector(); 93 | sel.getAllVersions = (): string[] => fakeGetVersionsResult; 94 | const matchedVersion = sel.findVersion(versionSpec); 95 | expect(matchedVersion).toBe(expected); 96 | }); 97 | }); 98 | 99 | describe("setVersion", () => { 100 | let coreExportVariableSpy: jest.SpyInstance; 101 | let fsExistsSpy: jest.SpyInstance; 102 | let fsInvokeCommandSpy: jest.SpyInstance; 103 | 104 | beforeEach(() => { 105 | coreExportVariableSpy = jest.spyOn(core, "exportVariable"); 106 | fsExistsSpy = jest.spyOn(fs, "existsSync"); 107 | fsInvokeCommandSpy = jest.spyOn(utils, "invokeCommandSync"); 108 | }); 109 | 110 | afterEach(() => { 111 | jest.resetAllMocks(); 112 | jest.clearAllMocks(); 113 | }); 114 | 115 | it("works correctly", () => { 116 | fsExistsSpy.mockImplementation(() => true); 117 | const sel = new XcodeSelector(); 118 | sel.setVersion("11.4"); 119 | expect(fsInvokeCommandSpy).toHaveBeenCalledWith("xcode-select", expect.any(Array), expect.any(Object)); 120 | expect(coreExportVariableSpy).toHaveBeenCalledWith("MD_APPLE_SDK_ROOT", expect.any(String)); 121 | }); 122 | 123 | it("error is thrown if version doesn't exist", () => { 124 | fsExistsSpy.mockImplementation(() => false); 125 | const sel = new XcodeSelector(); 126 | expect(() => sel.setVersion("11.4")).toThrow(); 127 | expect(fsInvokeCommandSpy).toHaveBeenCalledTimes(0); 128 | expect(coreExportVariableSpy).toHaveBeenCalledTimes(0); 129 | }); 130 | }); 131 | }); -------------------------------------------------------------------------------- /__tests__/xamarin-selector.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import * as core from "@actions/core"; 4 | import * as utils from "../src/utils"; 5 | import { MonoToolSelector } from "../src/mono-selector"; 6 | import { XamarinIosToolSelector } from "../src/xamarin-ios-selector"; 7 | import { XamarinMacToolSelector } from "../src/xamarin-mac-selector"; 8 | import { XamarinAndroidToolSelector } from "../src/xamarin-android-selector"; 9 | import { ToolSelector } from "../src/tool-selector"; 10 | import { VersionUtils } from "../src/version-utils"; 11 | 12 | jest.mock("fs"); 13 | jest.mock("@actions/core"); 14 | jest.mock("../src/utils"); 15 | 16 | const buildFsDirentItem = (name: string, opt: { isSymbolicLink: boolean; isDirectory: boolean }): fs.Dirent => { 17 | return { 18 | name, 19 | isSymbolicLink: () => opt.isSymbolicLink, 20 | isDirectory: () => opt.isDirectory 21 | } as fs.Dirent; 22 | }; 23 | 24 | const fakeReadDirResults = [ 25 | buildFsDirentItem("13.10", { isSymbolicLink: true, isDirectory: true }), 26 | buildFsDirentItem("13.10.0.21", { isSymbolicLink: false, isDirectory: true }), 27 | buildFsDirentItem("13.4", { isSymbolicLink: true, isDirectory: false }), 28 | buildFsDirentItem("13.4.0.2", { isSymbolicLink: false, isDirectory: true }), 29 | buildFsDirentItem("13.6", { isSymbolicLink: true, isDirectory: false }), 30 | buildFsDirentItem("13.6.0.12", { isSymbolicLink: false, isDirectory: true }), 31 | buildFsDirentItem("6_4_0", { isSymbolicLink: false, isDirectory: false }), 32 | buildFsDirentItem("6_6_0", { isSymbolicLink: true, isDirectory: true }), 33 | buildFsDirentItem("third_party_folder", { isSymbolicLink: false, isDirectory: true }), 34 | buildFsDirentItem("Current", { isSymbolicLink: true, isDirectory: true }), 35 | buildFsDirentItem("Latest", { isSymbolicLink: false, isDirectory: false }) 36 | ]; 37 | 38 | const fakeGetVersionsResult = VersionUtils.sortVersions([ 39 | "13.2.0.47", 40 | "13.4.0.2", 41 | "13.6.0.12", 42 | "13.8.2.9", 43 | "13.8.3.0", 44 | "13.8.3.2", 45 | "13.9.1.0", 46 | "13.10.0.21", 47 | "14.0.2.1" 48 | ]); 49 | 50 | describe.each([ 51 | MonoToolSelector, 52 | XamarinIosToolSelector, 53 | XamarinMacToolSelector, 54 | XamarinAndroidToolSelector 55 | ])("%s", (selectorClass: { new (): ToolSelector }) => { 56 | describe("getAllVersions", () => { 57 | 58 | beforeEach(() => { 59 | jest.spyOn(fs, "readdirSync").mockImplementation(() => fakeReadDirResults); 60 | jest.spyOn(fs, "readFileSync").mockImplementation(filepath => path.basename(path.dirname(filepath.toString()))); 61 | }); 62 | 63 | afterEach(() => { 64 | jest.resetAllMocks(); 65 | jest.clearAllMocks(); 66 | }); 67 | 68 | it("versions are filtered correctly", () => { 69 | const sel = new selectorClass(); 70 | const expectedVersions = [ 71 | "13.10.0.21", 72 | "13.6.0.12", 73 | "13.4.0.2" 74 | ]; 75 | expect(sel.getAllVersions()).toEqual(expectedVersions); 76 | }); 77 | }); 78 | 79 | describe("findVersion", () => { 80 | it.each([ 81 | ["latest", "14.0.2.1", "latest is matched"], 82 | ["14", "14.0.2.1", "one digit is matched"], 83 | ["13", "13.10.0.21", "one digit is matched and latest version is selected"], 84 | ["11", null, "one digit is not matched"], 85 | ["14.0", "14.0.2.1", "two digits are matched"], 86 | ["13.8", "13.8.3.2", "two digits are matched and latest version is selected"], 87 | ["13.7", null, "two digits are not matched"], 88 | ["11.0", null, "two digits are not matched"], 89 | ["13.2.0", "13.2.0.47", "three digits are matched"], 90 | ["13.8.3", "13.8.3.2", "three digits are matched and latest version is selected"], 91 | ["11.0.2", null, "three digits are not matched"], 92 | ["13.5.4", null, "three digits are not matched"], 93 | ["13.8.1", null, "three digits are not matched"], 94 | ["13.9.1.0", "13.9.1.0", "four digits are matched"], 95 | ["13.10.0.22", null, "four digits are not matched"] 96 | ] as [string, string | null, string][])("'%s' -> '%s' (%s)", (versionSpec: string, expected: string | null) => { 97 | const sel = new selectorClass(); 98 | sel.getAllVersions = (): string[] => fakeGetVersionsResult; 99 | const matchedVersion = sel.findVersion(versionSpec); 100 | expect(matchedVersion).toBe(expected); 101 | }); 102 | }); 103 | 104 | describe("setVersion", () => { 105 | let fsExistsSpy: jest.SpyInstance; 106 | let fsInvokeCommandSpy: jest.SpyInstance; 107 | 108 | beforeEach(() => { 109 | fsExistsSpy = jest.spyOn(fs, "existsSync"); 110 | fsInvokeCommandSpy = jest.spyOn(utils, "invokeCommandSync"); 111 | }); 112 | 113 | afterEach(() => { 114 | jest.resetAllMocks(); 115 | jest.clearAllMocks(); 116 | }); 117 | 118 | it("symlink is created", () => { 119 | fsExistsSpy.mockImplementation((path: string) => { 120 | return !path.endsWith("/Current"); 121 | }); 122 | const sel = new selectorClass(); 123 | sel.setVersion("1.2.3.4"); 124 | expect(fsInvokeCommandSpy).toHaveBeenCalledTimes(1); 125 | expect(fsInvokeCommandSpy).toHaveBeenCalledWith("ln", expect.any(Array), expect.any(Object)); 126 | }); 127 | 128 | it("symlink is recreated", () => { 129 | fsExistsSpy.mockImplementation(() => true); 130 | const sel = new selectorClass(); 131 | sel.setVersion("1.2.3.4"); 132 | expect(fsInvokeCommandSpy).toHaveBeenCalledTimes(2); 133 | expect(fsInvokeCommandSpy).toHaveBeenCalledWith("rm", expect.any(Array), expect.any(Object)); 134 | expect(fsInvokeCommandSpy).toHaveBeenCalledWith("ln", expect.any(Array), expect.any(Object)); 135 | }); 136 | 137 | it("error is thrown if version doesn't exist", () => { 138 | fsExistsSpy.mockImplementation(() => false); 139 | const sel = new selectorClass(); 140 | expect(() => sel.setVersion("1.2.3.4")).toThrow(); 141 | expect(fsInvokeCommandSpy).toHaveBeenCalledTimes(0); 142 | }); 143 | }); 144 | }); 145 | 146 | describe("MonoToolSelector", () => { 147 | describe("setVersion", () => { 148 | let coreExportVariableSpy: jest.SpyInstance; 149 | let coreAddPathSpy: jest.SpyInstance; 150 | let fsExistsSpy: jest.SpyInstance; 151 | 152 | beforeEach(() => { 153 | coreExportVariableSpy = jest.spyOn(core, "exportVariable"); 154 | coreAddPathSpy = jest.spyOn(core, "addPath"); 155 | fsExistsSpy = jest.spyOn(fs, "existsSync"); 156 | }); 157 | 158 | afterEach(() => { 159 | jest.resetAllMocks(); 160 | jest.clearAllMocks(); 161 | }); 162 | 163 | it("environment variables are set correctly", () => { 164 | fsExistsSpy.mockImplementation(() => true); 165 | 166 | const sel = new MonoToolSelector(); 167 | sel.setVersion("1.2.3.4"); 168 | expect(coreExportVariableSpy).toHaveBeenCalledWith("DYLD_LIBRARY_FALLBACK_PATH", expect.any(String)); 169 | expect(coreExportVariableSpy).toHaveBeenCalledWith("PKG_CONFIG_PATH", expect.any(String)); 170 | expect(coreAddPathSpy).toHaveBeenCalledWith("/Library/Frameworks/Mono.framework/Versions/1.2.3/bin"); 171 | }); 172 | }); 173 | }); 174 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | /******/ (function(modules, runtime) { // webpackBootstrap 3 | /******/ "use strict"; 4 | /******/ // The module cache 5 | /******/ var installedModules = {}; 6 | /******/ 7 | /******/ // The require function 8 | /******/ function __webpack_require__(moduleId) { 9 | /******/ 10 | /******/ // Check if module is in cache 11 | /******/ if(installedModules[moduleId]) { 12 | /******/ return installedModules[moduleId].exports; 13 | /******/ } 14 | /******/ // Create a new module (and put it into the cache) 15 | /******/ var module = installedModules[moduleId] = { 16 | /******/ i: moduleId, 17 | /******/ l: false, 18 | /******/ exports: {} 19 | /******/ }; 20 | /******/ 21 | /******/ // Execute the module function 22 | /******/ var threw = true; 23 | /******/ try { 24 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 25 | /******/ threw = false; 26 | /******/ } finally { 27 | /******/ if(threw) delete installedModules[moduleId]; 28 | /******/ } 29 | /******/ 30 | /******/ // Flag the module as loaded 31 | /******/ module.l = true; 32 | /******/ 33 | /******/ // Return the exports of the module 34 | /******/ return module.exports; 35 | /******/ } 36 | /******/ 37 | /******/ 38 | /******/ __webpack_require__.ab = __dirname + "/"; 39 | /******/ 40 | /******/ // the startup function 41 | /******/ function startup() { 42 | /******/ // Load entry module and return exports 43 | /******/ return __webpack_require__(299); 44 | /******/ }; 45 | /******/ 46 | /******/ // run startup 47 | /******/ return startup(); 48 | /******/ }) 49 | /************************************************************************/ 50 | /******/ ({ 51 | 52 | /***/ 82: 53 | /***/ (function(__unusedmodule, exports) { 54 | 55 | "use strict"; 56 | 57 | // We use any as a valid input type 58 | /* eslint-disable @typescript-eslint/no-explicit-any */ 59 | Object.defineProperty(exports, "__esModule", { value: true }); 60 | /** 61 | * Sanitizes an input into a string so it can be passed into issueCommand safely 62 | * @param input input to sanitize into a string 63 | */ 64 | function toCommandValue(input) { 65 | if (input === null || input === undefined) { 66 | return ''; 67 | } 68 | else if (typeof input === 'string' || input instanceof String) { 69 | return input; 70 | } 71 | return JSON.stringify(input); 72 | } 73 | exports.toCommandValue = toCommandValue; 74 | //# sourceMappingURL=utils.js.map 75 | 76 | /***/ }), 77 | 78 | /***/ 87: 79 | /***/ (function(module) { 80 | 81 | module.exports = require("os"); 82 | 83 | /***/ }), 84 | 85 | /***/ 102: 86 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 87 | 88 | "use strict"; 89 | 90 | // For internal use, subject to change. 91 | var __importStar = (this && this.__importStar) || function (mod) { 92 | if (mod && mod.__esModule) return mod; 93 | var result = {}; 94 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 95 | result["default"] = mod; 96 | return result; 97 | }; 98 | Object.defineProperty(exports, "__esModule", { value: true }); 99 | // We use any as a valid input type 100 | /* eslint-disable @typescript-eslint/no-explicit-any */ 101 | const fs = __importStar(__webpack_require__(747)); 102 | const os = __importStar(__webpack_require__(87)); 103 | const utils_1 = __webpack_require__(82); 104 | function issueCommand(command, message) { 105 | const filePath = process.env[`GITHUB_${command}`]; 106 | if (!filePath) { 107 | throw new Error(`Unable to find environment variable for file command ${command}`); 108 | } 109 | if (!fs.existsSync(filePath)) { 110 | throw new Error(`Missing file at path: ${filePath}`); 111 | } 112 | fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, { 113 | encoding: 'utf8' 114 | }); 115 | } 116 | exports.issueCommand = issueCommand; 117 | //# sourceMappingURL=file-command.js.map 118 | 119 | /***/ }), 120 | 121 | /***/ 116: 122 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 123 | 124 | "use strict"; 125 | 126 | Object.defineProperty(exports, "__esModule", { value: true }); 127 | exports.XamarinMacToolSelector = void 0; 128 | const xamarin_selector_1 = __webpack_require__(792); 129 | class XamarinMacToolSelector extends xamarin_selector_1.XamarinSelector { 130 | get toolName() { 131 | return "Xamarin.Mac"; 132 | } 133 | get basePath() { 134 | return "/Library/Frameworks/Xamarin.Mac.framework"; 135 | } 136 | } 137 | exports.XamarinMacToolSelector = XamarinMacToolSelector; 138 | 139 | 140 | /***/ }), 141 | 142 | /***/ 129: 143 | /***/ (function(module) { 144 | 145 | module.exports = require("child_process"); 146 | 147 | /***/ }), 148 | 149 | /***/ 182: 150 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 151 | 152 | "use strict"; 153 | 154 | Object.defineProperty(exports, "__esModule", { value: true }); 155 | exports.XamarinAndroidToolSelector = void 0; 156 | const xamarin_selector_1 = __webpack_require__(792); 157 | class XamarinAndroidToolSelector extends xamarin_selector_1.XamarinSelector { 158 | get toolName() { 159 | return "Xamarin.Android"; 160 | } 161 | get basePath() { 162 | return "/Library/Frameworks/Xamarin.Android.framework"; 163 | } 164 | } 165 | exports.XamarinAndroidToolSelector = XamarinAndroidToolSelector; 166 | 167 | 168 | /***/ }), 169 | 170 | /***/ 220: 171 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 172 | 173 | "use strict"; 174 | 175 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 176 | if (k2 === undefined) k2 = k; 177 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 178 | }) : (function(o, m, k, k2) { 179 | if (k2 === undefined) k2 = k; 180 | o[k2] = m[k]; 181 | })); 182 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 183 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 184 | }) : function(o, v) { 185 | o["default"] = v; 186 | }); 187 | var __importStar = (this && this.__importStar) || function (mod) { 188 | if (mod && mod.__esModule) return mod; 189 | var result = {}; 190 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 191 | __setModuleDefault(result, mod); 192 | return result; 193 | }; 194 | Object.defineProperty(exports, "__esModule", { value: true }); 195 | exports.MonoToolSelector = void 0; 196 | const fs = __importStar(__webpack_require__(747)); 197 | const path = __importStar(__webpack_require__(622)); 198 | const core = __importStar(__webpack_require__(470)); 199 | const xamarin_selector_1 = __webpack_require__(792); 200 | const version_utils_1 = __webpack_require__(957); 201 | class MonoToolSelector extends xamarin_selector_1.XamarinSelector { 202 | get basePath() { 203 | return "/Library/Frameworks/Mono.framework"; 204 | } 205 | get toolName() { 206 | return "Mono"; 207 | } 208 | getAllVersions() { 209 | const versionsFolders = super.getAllVersions(); 210 | // we have to look into '/Mono.Framework/Versions//Version' file for Mono to determine full version with 4 digits 211 | return versionsFolders.map(version => { 212 | const versionFile = path.join(this.versionsDirectoryPath, version, "Version"); 213 | const realVersion = fs.readFileSync(versionFile).toString(); 214 | return realVersion.trim(); 215 | }); 216 | } 217 | setVersion(version) { 218 | // for Mono, version folder contains only 3 digits instead of full version 219 | version = version_utils_1.VersionUtils.cutVersionLength(version, 3); 220 | super.setVersion(version); 221 | const versionDirectory = this.getVersionPath(version); 222 | core.exportVariable("DYLD_LIBRARY_FALLBACK_PATH", [ 223 | `${versionDirectory}/lib`, 224 | "/lib", 225 | "/usr/lib", 226 | process.env["DYLD_LIBRARY_FALLBACK_PATH"] 227 | ].join(path.delimiter)); 228 | core.exportVariable("PKG_CONFIG_PATH", [ 229 | `${versionDirectory}/lib/pkgconfig`, 230 | process.env["PKG_CONFIG_PATH"] 231 | ].join(path.delimiter)); 232 | core.debug(`Add '${versionDirectory}/bin' to PATH`); 233 | core.addPath(`${versionDirectory}/bin`); 234 | } 235 | } 236 | exports.MonoToolSelector = MonoToolSelector; 237 | 238 | 239 | /***/ }), 240 | 241 | /***/ 247: 242 | /***/ (function(module) { 243 | 244 | /* global define */ 245 | (function (root, factory) { 246 | /* istanbul ignore next */ 247 | if (typeof define === 'function' && define.amd) { 248 | define([], factory); 249 | } else if (true) { 250 | module.exports = factory(); 251 | } else {} 252 | }(this, function () { 253 | 254 | var semver = /^v?(?:\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+))?(?:-[\da-z\-]+(?:\.[\da-z\-]+)*)?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i; 255 | 256 | function indexOrEnd(str, q) { 257 | return str.indexOf(q) === -1 ? str.length : str.indexOf(q); 258 | } 259 | 260 | function split(v) { 261 | var c = v.replace(/^v/, '').replace(/\+.*$/, ''); 262 | var patchIndex = indexOrEnd(c, '-'); 263 | var arr = c.substring(0, patchIndex).split('.'); 264 | arr.push(c.substring(patchIndex + 1)); 265 | return arr; 266 | } 267 | 268 | function tryParse(v) { 269 | return isNaN(Number(v)) ? v : Number(v); 270 | } 271 | 272 | function validate(version) { 273 | if (typeof version !== 'string') { 274 | throw new TypeError('Invalid argument expected string'); 275 | } 276 | if (!semver.test(version)) { 277 | throw new Error('Invalid argument not valid semver (\''+version+'\' received)'); 278 | } 279 | } 280 | 281 | function compareVersions(v1, v2) { 282 | [v1, v2].forEach(validate); 283 | 284 | var s1 = split(v1); 285 | var s2 = split(v2); 286 | 287 | for (var i = 0; i < Math.max(s1.length - 1, s2.length - 1); i++) { 288 | var n1 = parseInt(s1[i] || 0, 10); 289 | var n2 = parseInt(s2[i] || 0, 10); 290 | 291 | if (n1 > n2) return 1; 292 | if (n2 > n1) return -1; 293 | } 294 | 295 | var sp1 = s1[s1.length - 1]; 296 | var sp2 = s2[s2.length - 1]; 297 | 298 | if (sp1 && sp2) { 299 | var p1 = sp1.split('.').map(tryParse); 300 | var p2 = sp2.split('.').map(tryParse); 301 | 302 | for (i = 0; i < Math.max(p1.length, p2.length); i++) { 303 | if (p1[i] === undefined || typeof p2[i] === 'string' && typeof p1[i] === 'number') return -1; 304 | if (p2[i] === undefined || typeof p1[i] === 'string' && typeof p2[i] === 'number') return 1; 305 | 306 | if (p1[i] > p2[i]) return 1; 307 | if (p2[i] > p1[i]) return -1; 308 | } 309 | } else if (sp1 || sp2) { 310 | return sp1 ? -1 : 1; 311 | } 312 | 313 | return 0; 314 | }; 315 | 316 | var allowedOperators = [ 317 | '>', 318 | '>=', 319 | '=', 320 | '<', 321 | '<=' 322 | ]; 323 | 324 | var operatorResMap = { 325 | '>': [1], 326 | '>=': [0, 1], 327 | '=': [0], 328 | '<=': [-1, 0], 329 | '<': [-1] 330 | }; 331 | 332 | function validateOperator(op) { 333 | if (typeof op !== 'string') { 334 | throw new TypeError('Invalid operator type, expected string but got ' + typeof op); 335 | } 336 | if (allowedOperators.indexOf(op) === -1) { 337 | throw new TypeError('Invalid operator, expected one of ' + allowedOperators.join('|')); 338 | } 339 | } 340 | 341 | compareVersions.validate = function(version) { 342 | return typeof version === 'string' && semver.test(version); 343 | } 344 | 345 | compareVersions.compare = function (v1, v2, operator) { 346 | // Validate operator 347 | validateOperator(operator); 348 | 349 | // since result of compareVersions can only be -1 or 0 or 1 350 | // a simple map can be used to replace switch 351 | var res = compareVersions(v1, v2); 352 | return operatorResMap[operator].indexOf(res) > -1; 353 | } 354 | 355 | return compareVersions; 356 | })); 357 | 358 | 359 | /***/ }), 360 | 361 | /***/ 299: 362 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 363 | 364 | "use strict"; 365 | 366 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 367 | if (k2 === undefined) k2 = k; 368 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 369 | }) : (function(o, m, k, k2) { 370 | if (k2 === undefined) k2 = k; 371 | o[k2] = m[k]; 372 | })); 373 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 374 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 375 | }) : function(o, v) { 376 | o["default"] = v; 377 | }); 378 | var __importStar = (this && this.__importStar) || function (mod) { 379 | if (mod && mod.__esModule) return mod; 380 | var result = {}; 381 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 382 | __setModuleDefault(result, mod); 383 | return result; 384 | }; 385 | Object.defineProperty(exports, "__esModule", { value: true }); 386 | const core = __importStar(__webpack_require__(470)); 387 | const mono_selector_1 = __webpack_require__(220); 388 | const xamarin_ios_selector_1 = __webpack_require__(497); 389 | const xamarin_mac_selector_1 = __webpack_require__(116); 390 | const xamarin_android_selector_1 = __webpack_require__(182); 391 | const os_1 = __webpack_require__(87); 392 | const version_utils_1 = __webpack_require__(957); 393 | const xcode_selector_1 = __webpack_require__(670); 394 | let showVersionMajorMinorWarning = false; 395 | const invokeSelector = (variableName, toolSelector) => { 396 | const versionSpec = core.getInput(variableName, { required: false }); 397 | if (!versionSpec) { 398 | return; 399 | } 400 | const selector = new toolSelector(); 401 | if (!version_utils_1.VersionUtils.isLatestVersionKeyword(versionSpec) && !version_utils_1.VersionUtils.isValidVersion(versionSpec)) { 402 | throw new Error(`Value '${versionSpec}' is not valid version for ${selector.toolName}`); 403 | } 404 | core.info(`Switching ${selector.toolName} to version '${versionSpec}'...`); 405 | const targetVersion = selector.findVersion(versionSpec); 406 | if (!targetVersion) { 407 | throw new Error([ 408 | `Could not find ${selector.toolName} version that satisfied version spec: ${versionSpec}`, 409 | "Available versions:", 410 | ...selector.getAllVersions().map(ver => `- ${ver}`) 411 | ].join(os_1.EOL)); 412 | } 413 | core.debug(`${selector.toolName} ${targetVersion} will be set`); 414 | selector.setVersion(targetVersion); 415 | core.info(`${selector.toolName} is set to '${targetVersion}'`); 416 | showVersionMajorMinorWarning = showVersionMajorMinorWarning || version_utils_1.VersionUtils.countVersionLength(versionSpec) > 2; 417 | }; 418 | const run = () => { 419 | try { 420 | if (process.platform !== "darwin") { 421 | throw new Error(`This task is intended only for macOS platform. It can't be run on '${process.platform}' platform`); 422 | } 423 | invokeSelector("mono-version", mono_selector_1.MonoToolSelector); 424 | invokeSelector("xamarin-ios-version", xamarin_ios_selector_1.XamarinIosToolSelector); 425 | invokeSelector("xamarin-mac-version", xamarin_mac_selector_1.XamarinMacToolSelector); 426 | invokeSelector("xamarin-android-version", xamarin_android_selector_1.XamarinAndroidToolSelector); 427 | invokeSelector("xcode-version", xcode_selector_1.XcodeSelector); 428 | if (showVersionMajorMinorWarning) { 429 | core.warning([ 430 | "It is recommended to specify only major and minor versions of tool (like '13' or '13.2').", 431 | "Hosted VMs contain the latest patch & build version for each major & minor pair.", 432 | "It means that version '13.2.1.4' can be replaced by '13.2.2.0' without any notice and your pipeline will start failing." 433 | ].join(" ")); 434 | } 435 | } 436 | catch (error) { 437 | core.setFailed(error.message); 438 | } 439 | }; 440 | run(); 441 | 442 | 443 | /***/ }), 444 | 445 | /***/ 431: 446 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 447 | 448 | "use strict"; 449 | 450 | var __importStar = (this && this.__importStar) || function (mod) { 451 | if (mod && mod.__esModule) return mod; 452 | var result = {}; 453 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 454 | result["default"] = mod; 455 | return result; 456 | }; 457 | Object.defineProperty(exports, "__esModule", { value: true }); 458 | const os = __importStar(__webpack_require__(87)); 459 | const utils_1 = __webpack_require__(82); 460 | /** 461 | * Commands 462 | * 463 | * Command Format: 464 | * ::name key=value,key=value::message 465 | * 466 | * Examples: 467 | * ::warning::This is the message 468 | * ::set-env name=MY_VAR::some value 469 | */ 470 | function issueCommand(command, properties, message) { 471 | const cmd = new Command(command, properties, message); 472 | process.stdout.write(cmd.toString() + os.EOL); 473 | } 474 | exports.issueCommand = issueCommand; 475 | function issue(name, message = '') { 476 | issueCommand(name, {}, message); 477 | } 478 | exports.issue = issue; 479 | const CMD_STRING = '::'; 480 | class Command { 481 | constructor(command, properties, message) { 482 | if (!command) { 483 | command = 'missing.command'; 484 | } 485 | this.command = command; 486 | this.properties = properties; 487 | this.message = message; 488 | } 489 | toString() { 490 | let cmdStr = CMD_STRING + this.command; 491 | if (this.properties && Object.keys(this.properties).length > 0) { 492 | cmdStr += ' '; 493 | let first = true; 494 | for (const key in this.properties) { 495 | if (this.properties.hasOwnProperty(key)) { 496 | const val = this.properties[key]; 497 | if (val) { 498 | if (first) { 499 | first = false; 500 | } 501 | else { 502 | cmdStr += ','; 503 | } 504 | cmdStr += `${key}=${escapeProperty(val)}`; 505 | } 506 | } 507 | } 508 | } 509 | cmdStr += `${CMD_STRING}${escapeData(this.message)}`; 510 | return cmdStr; 511 | } 512 | } 513 | function escapeData(s) { 514 | return utils_1.toCommandValue(s) 515 | .replace(/%/g, '%25') 516 | .replace(/\r/g, '%0D') 517 | .replace(/\n/g, '%0A'); 518 | } 519 | function escapeProperty(s) { 520 | return utils_1.toCommandValue(s) 521 | .replace(/%/g, '%25') 522 | .replace(/\r/g, '%0D') 523 | .replace(/\n/g, '%0A') 524 | .replace(/:/g, '%3A') 525 | .replace(/,/g, '%2C'); 526 | } 527 | //# sourceMappingURL=command.js.map 528 | 529 | /***/ }), 530 | 531 | /***/ 470: 532 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 533 | 534 | "use strict"; 535 | 536 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 537 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 538 | return new (P || (P = Promise))(function (resolve, reject) { 539 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 540 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 541 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 542 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 543 | }); 544 | }; 545 | var __importStar = (this && this.__importStar) || function (mod) { 546 | if (mod && mod.__esModule) return mod; 547 | var result = {}; 548 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 549 | result["default"] = mod; 550 | return result; 551 | }; 552 | Object.defineProperty(exports, "__esModule", { value: true }); 553 | const command_1 = __webpack_require__(431); 554 | const file_command_1 = __webpack_require__(102); 555 | const utils_1 = __webpack_require__(82); 556 | const os = __importStar(__webpack_require__(87)); 557 | const path = __importStar(__webpack_require__(622)); 558 | /** 559 | * The code to exit an action 560 | */ 561 | var ExitCode; 562 | (function (ExitCode) { 563 | /** 564 | * A code indicating that the action was successful 565 | */ 566 | ExitCode[ExitCode["Success"] = 0] = "Success"; 567 | /** 568 | * A code indicating that the action was a failure 569 | */ 570 | ExitCode[ExitCode["Failure"] = 1] = "Failure"; 571 | })(ExitCode = exports.ExitCode || (exports.ExitCode = {})); 572 | //----------------------------------------------------------------------- 573 | // Variables 574 | //----------------------------------------------------------------------- 575 | /** 576 | * Sets env variable for this action and future actions in the job 577 | * @param name the name of the variable to set 578 | * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify 579 | */ 580 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 581 | function exportVariable(name, val) { 582 | const convertedVal = utils_1.toCommandValue(val); 583 | process.env[name] = convertedVal; 584 | const filePath = process.env['GITHUB_ENV'] || ''; 585 | if (filePath) { 586 | const delimiter = '_GitHubActionsFileCommandDelimeter_'; 587 | const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; 588 | file_command_1.issueCommand('ENV', commandValue); 589 | } 590 | else { 591 | command_1.issueCommand('set-env', { name }, convertedVal); 592 | } 593 | } 594 | exports.exportVariable = exportVariable; 595 | /** 596 | * Registers a secret which will get masked from logs 597 | * @param secret value of the secret 598 | */ 599 | function setSecret(secret) { 600 | command_1.issueCommand('add-mask', {}, secret); 601 | } 602 | exports.setSecret = setSecret; 603 | /** 604 | * Prepends inputPath to the PATH (for this action and future actions) 605 | * @param inputPath 606 | */ 607 | function addPath(inputPath) { 608 | const filePath = process.env['GITHUB_PATH'] || ''; 609 | if (filePath) { 610 | file_command_1.issueCommand('PATH', inputPath); 611 | } 612 | else { 613 | command_1.issueCommand('add-path', {}, inputPath); 614 | } 615 | process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; 616 | } 617 | exports.addPath = addPath; 618 | /** 619 | * Gets the value of an input. The value is also trimmed. 620 | * 621 | * @param name name of the input to get 622 | * @param options optional. See InputOptions. 623 | * @returns string 624 | */ 625 | function getInput(name, options) { 626 | const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; 627 | if (options && options.required && !val) { 628 | throw new Error(`Input required and not supplied: ${name}`); 629 | } 630 | return val.trim(); 631 | } 632 | exports.getInput = getInput; 633 | /** 634 | * Sets the value of an output. 635 | * 636 | * @param name name of the output to set 637 | * @param value value to store. Non-string values will be converted to a string via JSON.stringify 638 | */ 639 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 640 | function setOutput(name, value) { 641 | command_1.issueCommand('set-output', { name }, value); 642 | } 643 | exports.setOutput = setOutput; 644 | /** 645 | * Enables or disables the echoing of commands into stdout for the rest of the step. 646 | * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. 647 | * 648 | */ 649 | function setCommandEcho(enabled) { 650 | command_1.issue('echo', enabled ? 'on' : 'off'); 651 | } 652 | exports.setCommandEcho = setCommandEcho; 653 | //----------------------------------------------------------------------- 654 | // Results 655 | //----------------------------------------------------------------------- 656 | /** 657 | * Sets the action status to failed. 658 | * When the action exits it will be with an exit code of 1 659 | * @param message add error issue message 660 | */ 661 | function setFailed(message) { 662 | process.exitCode = ExitCode.Failure; 663 | error(message); 664 | } 665 | exports.setFailed = setFailed; 666 | //----------------------------------------------------------------------- 667 | // Logging Commands 668 | //----------------------------------------------------------------------- 669 | /** 670 | * Gets whether Actions Step Debug is on or not 671 | */ 672 | function isDebug() { 673 | return process.env['RUNNER_DEBUG'] === '1'; 674 | } 675 | exports.isDebug = isDebug; 676 | /** 677 | * Writes debug message to user log 678 | * @param message debug message 679 | */ 680 | function debug(message) { 681 | command_1.issueCommand('debug', {}, message); 682 | } 683 | exports.debug = debug; 684 | /** 685 | * Adds an error issue 686 | * @param message error issue message. Errors will be converted to string via toString() 687 | */ 688 | function error(message) { 689 | command_1.issue('error', message instanceof Error ? message.toString() : message); 690 | } 691 | exports.error = error; 692 | /** 693 | * Adds an warning issue 694 | * @param message warning issue message. Errors will be converted to string via toString() 695 | */ 696 | function warning(message) { 697 | command_1.issue('warning', message instanceof Error ? message.toString() : message); 698 | } 699 | exports.warning = warning; 700 | /** 701 | * Writes info to log with console.log. 702 | * @param message info message 703 | */ 704 | function info(message) { 705 | process.stdout.write(message + os.EOL); 706 | } 707 | exports.info = info; 708 | /** 709 | * Begin an output group. 710 | * 711 | * Output until the next `groupEnd` will be foldable in this group 712 | * 713 | * @param name The name of the output group 714 | */ 715 | function startGroup(name) { 716 | command_1.issue('group', name); 717 | } 718 | exports.startGroup = startGroup; 719 | /** 720 | * End an output group. 721 | */ 722 | function endGroup() { 723 | command_1.issue('endgroup'); 724 | } 725 | exports.endGroup = endGroup; 726 | /** 727 | * Wrap an asynchronous function call in a group. 728 | * 729 | * Returns the same type as the function itself. 730 | * 731 | * @param name The name of the group 732 | * @param fn The function to wrap in the group 733 | */ 734 | function group(name, fn) { 735 | return __awaiter(this, void 0, void 0, function* () { 736 | startGroup(name); 737 | let result; 738 | try { 739 | result = yield fn(); 740 | } 741 | finally { 742 | endGroup(); 743 | } 744 | return result; 745 | }); 746 | } 747 | exports.group = group; 748 | //----------------------------------------------------------------------- 749 | // Wrapper action state 750 | //----------------------------------------------------------------------- 751 | /** 752 | * Saves state for current action, the state can only be retrieved by this action's post job execution. 753 | * 754 | * @param name name of the state to store 755 | * @param value value to store. Non-string values will be converted to a string via JSON.stringify 756 | */ 757 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 758 | function saveState(name, value) { 759 | command_1.issueCommand('save-state', { name }, value); 760 | } 761 | exports.saveState = saveState; 762 | /** 763 | * Gets the value of an state set by this action's main execution. 764 | * 765 | * @param name name of the state to get 766 | * @returns string 767 | */ 768 | function getState(name) { 769 | return process.env[`STATE_${name}`] || ''; 770 | } 771 | exports.getState = getState; 772 | //# sourceMappingURL=core.js.map 773 | 774 | /***/ }), 775 | 776 | /***/ 497: 777 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 778 | 779 | "use strict"; 780 | 781 | Object.defineProperty(exports, "__esModule", { value: true }); 782 | exports.XamarinIosToolSelector = void 0; 783 | const xamarin_selector_1 = __webpack_require__(792); 784 | class XamarinIosToolSelector extends xamarin_selector_1.XamarinSelector { 785 | get toolName() { 786 | return "Xamarin.iOS"; 787 | } 788 | get basePath() { 789 | return "/Library/Frameworks/Xamarin.iOS.framework"; 790 | } 791 | } 792 | exports.XamarinIosToolSelector = XamarinIosToolSelector; 793 | 794 | 795 | /***/ }), 796 | 797 | /***/ 611: 798 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 799 | 800 | "use strict"; 801 | 802 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 803 | if (k2 === undefined) k2 = k; 804 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 805 | }) : (function(o, m, k, k2) { 806 | if (k2 === undefined) k2 = k; 807 | o[k2] = m[k]; 808 | })); 809 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 810 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 811 | }) : function(o, v) { 812 | o["default"] = v; 813 | }); 814 | var __importStar = (this && this.__importStar) || function (mod) { 815 | if (mod && mod.__esModule) return mod; 816 | var result = {}; 817 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 818 | __setModuleDefault(result, mod); 819 | return result; 820 | }; 821 | Object.defineProperty(exports, "__esModule", { value: true }); 822 | exports.invokeCommandSync = void 0; 823 | const child = __importStar(__webpack_require__(129)); 824 | const os_1 = __webpack_require__(87); 825 | exports.invokeCommandSync = (command, args, options) => { 826 | let execResult; 827 | if (options.sudo) { 828 | execResult = child.spawnSync("sudo", [command, ...args]); 829 | } 830 | else { 831 | execResult = child.spawnSync(command, args); 832 | } 833 | if (execResult.status !== 0) { 834 | const fullCommand = `${options.sudo ? "sudo " : ""}${command} ${args.join(" ")}`; 835 | throw new Error([ 836 | `Error during run '${fullCommand}'`, 837 | execResult.stderr, 838 | execResult.stdout 839 | ].join(os_1.EOL)); 840 | } 841 | }; 842 | 843 | 844 | /***/ }), 845 | 846 | /***/ 622: 847 | /***/ (function(module) { 848 | 849 | module.exports = require("path"); 850 | 851 | /***/ }), 852 | 853 | /***/ 670: 854 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 855 | 856 | "use strict"; 857 | 858 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 859 | if (k2 === undefined) k2 = k; 860 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 861 | }) : (function(o, m, k, k2) { 862 | if (k2 === undefined) k2 = k; 863 | o[k2] = m[k]; 864 | })); 865 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 866 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 867 | }) : function(o, v) { 868 | o["default"] = v; 869 | }); 870 | var __importStar = (this && this.__importStar) || function (mod) { 871 | if (mod && mod.__esModule) return mod; 872 | var result = {}; 873 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 874 | __setModuleDefault(result, mod); 875 | return result; 876 | }; 877 | Object.defineProperty(exports, "__esModule", { value: true }); 878 | exports.XcodeSelector = void 0; 879 | const fs = __importStar(__webpack_require__(747)); 880 | const path = __importStar(__webpack_require__(622)); 881 | const core = __importStar(__webpack_require__(470)); 882 | const version_utils_1 = __webpack_require__(957); 883 | const utils = __importStar(__webpack_require__(611)); 884 | class XcodeSelector { 885 | constructor() { 886 | this.xcodeDirectoryPath = "/Applications"; 887 | this.xcodeFilenameRegex = /Xcode_([\d.]+)(_beta)?\.app/; 888 | } 889 | parseXcodeVersionFromFilename(filename) { 890 | const match = filename.match(this.xcodeFilenameRegex); 891 | if (!match || match.length < 2) { 892 | return null; 893 | } 894 | return match[1]; 895 | } 896 | get toolName() { 897 | return "Xcode"; 898 | } 899 | getVersionPath(version) { 900 | return path.join(this.xcodeDirectoryPath, `Xcode_${version}.app`); 901 | } 902 | getAllVersions() { 903 | const children = fs.readdirSync(this.xcodeDirectoryPath, { encoding: "utf8", withFileTypes: true }); 904 | let potentialVersions = children.filter(child => !child.isSymbolicLink() && child.isDirectory()).map(child => child.name); 905 | potentialVersions = potentialVersions.map(child => this.parseXcodeVersionFromFilename(child)).filter((child) => !!child); 906 | const stableVersions = potentialVersions.filter(ver => version_utils_1.VersionUtils.isValidVersion(ver)); 907 | const betaVersions = potentialVersions.filter(ver => ver.endsWith("_beta")).map(ver => { 908 | var _a; 909 | const verWithoutBeta = ver.substr(0, ver.length - 5); 910 | return (_a = children.find(child => child.isSymbolicLink() && this.parseXcodeVersionFromFilename(child.name) === verWithoutBeta)) === null || _a === void 0 ? void 0 : _a.name; 911 | }).filter(((ver) => !!ver && version_utils_1.VersionUtils.isValidVersion(ver))); 912 | // sort versions array by descending to make sure that the newest version will be picked up 913 | return version_utils_1.VersionUtils.sortVersions([...stableVersions, ...betaVersions]); 914 | } 915 | findVersion(versionSpec) { 916 | var _a; 917 | const availableVersions = this.getAllVersions(); 918 | if (availableVersions.length === 0) { 919 | return null; 920 | } 921 | if (version_utils_1.VersionUtils.isLatestVersionKeyword(versionSpec)) { 922 | return availableVersions[0]; 923 | } 924 | return (_a = availableVersions.find(ver => version_utils_1.VersionUtils.isVersionsEqual(ver, versionSpec))) !== null && _a !== void 0 ? _a : null; 925 | } 926 | setVersion(version) { 927 | const targetVersionDirectory = this.getVersionPath(version); 928 | if (!fs.existsSync(targetVersionDirectory)) { 929 | throw new Error(`Invalid version: Directory '${targetVersionDirectory}' doesn't exist`); 930 | } 931 | core.debug(`sudo xcode-select -s ${targetVersionDirectory}`); 932 | utils.invokeCommandSync("xcode-select", ["-s", targetVersionDirectory], { sudo: true }); 933 | core.exportVariable("MD_APPLE_SDK_ROOT", targetVersionDirectory); 934 | } 935 | } 936 | exports.XcodeSelector = XcodeSelector; 937 | 938 | 939 | /***/ }), 940 | 941 | /***/ 747: 942 | /***/ (function(module) { 943 | 944 | module.exports = require("fs"); 945 | 946 | /***/ }), 947 | 948 | /***/ 792: 949 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 950 | 951 | "use strict"; 952 | 953 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 954 | if (k2 === undefined) k2 = k; 955 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 956 | }) : (function(o, m, k, k2) { 957 | if (k2 === undefined) k2 = k; 958 | o[k2] = m[k]; 959 | })); 960 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 961 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 962 | }) : function(o, v) { 963 | o["default"] = v; 964 | }); 965 | var __importStar = (this && this.__importStar) || function (mod) { 966 | if (mod && mod.__esModule) return mod; 967 | var result = {}; 968 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 969 | __setModuleDefault(result, mod); 970 | return result; 971 | }; 972 | Object.defineProperty(exports, "__esModule", { value: true }); 973 | exports.XamarinSelector = void 0; 974 | const fs = __importStar(__webpack_require__(747)); 975 | const path = __importStar(__webpack_require__(622)); 976 | const core = __importStar(__webpack_require__(470)); 977 | const utils = __importStar(__webpack_require__(611)); 978 | const version_utils_1 = __webpack_require__(957); 979 | class XamarinSelector { 980 | get versionsDirectoryPath() { 981 | return path.join(this.basePath, "Versions"); 982 | } 983 | getVersionPath(version) { 984 | return path.join(this.versionsDirectoryPath, version); 985 | } 986 | getAllVersions() { 987 | const children = fs.readdirSync(this.versionsDirectoryPath, { encoding: "utf8", withFileTypes: true }); 988 | // macOS image contains symlinks for full versions, like '13.2' -> '13.2.3.0' 989 | // filter such symlinks and look for only real versions 990 | let potentialVersions = children.filter(child => !child.isSymbolicLink() && child.isDirectory()).map(child => child.name); 991 | potentialVersions = potentialVersions.filter(child => version_utils_1.VersionUtils.isValidVersion(child)); 992 | // sort versions array by descending to make sure that the newest version will be picked up 993 | return version_utils_1.VersionUtils.sortVersions(potentialVersions); 994 | } 995 | findVersion(versionSpec) { 996 | var _a; 997 | const availableVersions = this.getAllVersions(); 998 | if (availableVersions.length === 0) { 999 | return null; 1000 | } 1001 | if (version_utils_1.VersionUtils.isLatestVersionKeyword(versionSpec)) { 1002 | return availableVersions[0]; 1003 | } 1004 | const normalizedVersionSpec = version_utils_1.VersionUtils.normalizeVersion(versionSpec); 1005 | core.debug(`Semantic version spec of '${versionSpec}' is '${normalizedVersionSpec}'`); 1006 | return (_a = availableVersions.find(ver => version_utils_1.VersionUtils.isVersionsEqual(ver, normalizedVersionSpec))) !== null && _a !== void 0 ? _a : null; 1007 | } 1008 | setVersion(version) { 1009 | const targetVersionDirectory = this.getVersionPath(version); 1010 | if (!fs.existsSync(targetVersionDirectory)) { 1011 | throw new Error(`Invalid version: Directory '${targetVersionDirectory}' doesn't exist`); 1012 | } 1013 | const currentVersionDirectory = path.join(this.versionsDirectoryPath, "Current"); 1014 | core.debug(`Creating symlink '${currentVersionDirectory}' -> '${targetVersionDirectory}'`); 1015 | if (fs.existsSync(currentVersionDirectory)) { 1016 | utils.invokeCommandSync("rm", ["-f", currentVersionDirectory], { sudo: true }); 1017 | } 1018 | utils.invokeCommandSync("ln", ["-s", targetVersionDirectory, currentVersionDirectory], { sudo: true }); 1019 | } 1020 | static toString() { 1021 | // show correct name for test suite 1022 | return this.name; 1023 | } 1024 | } 1025 | exports.XamarinSelector = XamarinSelector; 1026 | 1027 | 1028 | /***/ }), 1029 | 1030 | /***/ 957: 1031 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 1032 | 1033 | "use strict"; 1034 | 1035 | var __importDefault = (this && this.__importDefault) || function (mod) { 1036 | return (mod && mod.__esModule) ? mod : { "default": mod }; 1037 | }; 1038 | Object.defineProperty(exports, "__esModule", { value: true }); 1039 | exports.VersionUtils = void 0; 1040 | const compare_versions_1 = __importDefault(__webpack_require__(247)); 1041 | class VersionUtils { 1042 | } 1043 | exports.VersionUtils = VersionUtils; 1044 | VersionUtils.isValidVersion = (version) => { 1045 | return compare_versions_1.default.validate(version); 1046 | }; 1047 | VersionUtils.isLatestVersionKeyword = (version) => { 1048 | return version === "latest"; 1049 | }; 1050 | VersionUtils.isVersionsEqual = (firstVersion, secondVersion) => { 1051 | return compare_versions_1.default.compare(firstVersion, secondVersion, "="); 1052 | }; 1053 | VersionUtils.sortVersions = (versions) => { 1054 | return [...versions].sort(compare_versions_1.default).reverse(); 1055 | }; 1056 | VersionUtils.normalizeVersion = (version) => { 1057 | const versionParts = VersionUtils.splitVersionToParts(version); 1058 | while (versionParts.length < 4) { 1059 | versionParts.push("x"); 1060 | } 1061 | return VersionUtils.buildVersionFromParts(versionParts); 1062 | }; 1063 | VersionUtils.countVersionLength = (version) => { 1064 | return VersionUtils.splitVersionToParts(version).length; 1065 | }; 1066 | VersionUtils.cutVersionLength = (version, newLength) => { 1067 | const versionParts = VersionUtils.splitVersionToParts(version); 1068 | const newParts = versionParts.slice(0, newLength); 1069 | return VersionUtils.buildVersionFromParts(newParts); 1070 | }; 1071 | VersionUtils.splitVersionToParts = (version) => { 1072 | return version.split("."); 1073 | }; 1074 | VersionUtils.buildVersionFromParts = (versionParts) => { 1075 | return versionParts.join("."); 1076 | }; 1077 | 1078 | 1079 | /***/ }) 1080 | 1081 | /******/ }); --------------------------------------------------------------------------------