diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ee76c2..7a8ba18 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -148,3 +148,22 @@ jobs: required-gazebo-distributions: 'harmonic' - name: 'Test Gazebo installation' run: 'gz sim --versions' + + test_gazebo_install_windows: + name: 'Check installation of Gazebo on Windows' + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4.0.2 + with: + node-version: '20.x' + - uses: conda-incubator/setup-miniconda@v3 + - name: 'Check Gazebo installation on Windows runner' + uses: ./ + with: + required-gazebo-distributions: 'harmonic' + - name: 'Test Gazebo installation' + shell: pwsh + run: | + conda activate + gz sim --versions \ No newline at end of file diff --git a/README.md b/README.md index 7d2b213..9d7d3b9 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,10 @@ This action sets up a Gazebo release inside a Linux environment. 1. [Setting up worker and installing a compatible Gazebo and Ubuntu combination](#Setting-up-worker-and-installing-a-compatible-Gazebo-and-Ubuntu-combination) 1. [Iterating on all Gazebo and Ubuntu combinations](#Iterating-on-all-gazebo-ubuntu-combinations) 1. [Using pre-release and/or nightly Gazebo binaries](#Using-pre-release-and/or-nightly-Gazebo-binaries) - 2. [MacOS](#MacOS) + 2. [macOS](#macOS) 1. [Setting up worker to install Gazebo on macOS](#Setting-up-worker-to-install-Gazebo-on-macOS) + 3. [Windows](#Windows) + 1. [Setting up worker to install Gazebo on Windows](#Setting-up-worker-to-install-Gazebo-on-Windows) 1. [License](#License) ## Overview @@ -25,6 +27,7 @@ It is recommended to use the `setup-gazebo` action inside a Docker container due `setup-gazebo` action works for all non-EOL Gazebo [releases] on the following platforms: - Ubuntu - macOS + - Windows ## Tasks performed by the action @@ -36,6 +39,8 @@ The `setup-gazebo` action performs the following tasks: - Registers the Open Robotics APT repository - On macOS: - Tapping into the [osrf/homebrew-simulation](https://github.com/osrf/homebrew-simulation) using Homebrew +- On Windows: + - Installing Gazebo using Conda from conda-forge ## Usage See [action.yml](action.yml) @@ -155,7 +160,7 @@ This workflow shows how to use binaries from [pre-release] or [nightly] Gazebo r run: 'gz sim --versions' ``` -### MacOS +### macOS #### Setting up worker to install Gazebo on macOS @@ -177,6 +182,32 @@ This workflow shows how to install Gazebo on a macOS worker. The action needs an run: 'gz sim --versions' ``` +### Windows + +This workflow shows how to install Gazebo on a Windows worker. The action requires a Conda package management system such as miniconda as all Gazebo packages are available on conda-forge. The action is run by specifying the distribution of choice in `required-gazebo-distributions` field. + +#### Setting up worker to install Gazebo on Windows + +```yaml + test_gazebo: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4.0.2 + with: + node-version: '20.x' + - uses: conda-incubator/setup-miniconda@v3 + - name: 'Check Gazebo installation on Windows runner' + uses: gazebo-tooling/setup-gazebo@ + with: + required-gazebo-distributions: 'harmonic' + - name: 'Test Gazebo installation' + shell: pwsh + run: | + conda activate + gz sim --versions +``` + ## License The scripts and documentation in this project are released under the [Apache 2](LICENSE) license. diff --git a/__test__/main.test.ts b/__test__/main.test.ts index 69c5b07..a1023df 100644 --- a/__test__/main.test.ts +++ b/__test__/main.test.ts @@ -3,6 +3,7 @@ import * as exec from "@actions/exec"; import * as linux from "../src/setup-gazebo-linux"; import * as macOs from "../src/setup-gazebo-macos"; +import * as windows from "../src/setup-gazebo-windows"; import * as utils from "../src/utils"; describe("workflow test without input", () => { @@ -21,6 +22,10 @@ describe("workflow test without input", () => { it("run macOS workflow without input", async () => { await expect(macOs.runMacOs()).rejects.toThrow(); }); + + it("run Windows workflow without input", async () => { + await expect(windows.runWindows()).rejects.toThrow(); + }); }); describe("workflow test with a invalid distro input", () => { @@ -40,6 +45,10 @@ describe("workflow test with a invalid distro input", () => { it("run macOS workflow with invalid distro input", async () => { await expect(macOs.runMacOs()).rejects.toThrow(); }); + + it("run Windows workflow with invalid distro input", async () => { + await expect(windows.runWindows()).rejects.toThrow(); + }); }); describe("workflow test with a valid distro input", () => { @@ -59,6 +68,10 @@ describe("workflow test with a valid distro input", () => { it("run macOS workflow with valid distro input", async () => { await expect(macOs.runMacOs()).resolves.not.toThrow(); }); + + it("run Windows workflow with valid distro input", async () => { + await expect(windows.runWindows()).resolves.not.toThrow(); + }); }); describe("validate distribution test", () => { diff --git a/dist/index.js b/dist/index.js index 2362926..72f290e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -26289,6 +26289,61 @@ function linkPackage(packageName) { } +/***/ }), + +/***/ 7725: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.runConda = runConda; +const utils = __importStar(__nccwpck_require__(1314)); +/** + * Run conda install on a list of specified packages. + * + * @param packages list of conda-forge packages to be installed + * @returns Promise exit code + */ +function runConda(packages) { + return __awaiter(this, void 0, void 0, function* () { + return utils.exec("conda", ["install", "--channel", "conda-forge"].concat(packages)); + }); +} + + /***/ }), /***/ 5467: @@ -26525,6 +26580,95 @@ function runMacOs() { } +/***/ }), + +/***/ 8502: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.runWindows = runWindows; +const utils = __importStar(__nccwpck_require__(1314)); +const conda = __importStar(__nccwpck_require__(7725)); +// List of mapped Gazebo distro to gz-sim versions +const validLibVersions = [ + { + distro: "garden", + libVersion: 7, + }, + { + distro: "harmonic", + libVersion: 8, + }, +]; +/** + * Get gz-sim library version corresponding to a gz-$collection + * since conda does not currently support the gz-$collection metapackages. + * See https://github.com/conda-forge/gz-sim-feedstock/issues/61 + * + * @param gazeboDistro name of Gazebo distribution + * @returns gz-sim version + */ +function getLibVersion(gazeboDistro) { + return __awaiter(this, void 0, void 0, function* () { + let version; + validLibVersions.forEach((obj) => { + if (obj.distro == gazeboDistro) { + version = obj.libVersion; + } + }); + if (version === undefined) { + throw new Error(`No conda packages available for gz-${gazeboDistro}`); + } + return version; + }); +} +/** + * Install Gazebo on a Windows worker + */ +function runWindows() { + return __awaiter(this, void 0, void 0, function* () { + for (const gazeboDistro of utils.getRequiredGazeboDistributions()) { + const version = yield getLibVersion(gazeboDistro); + yield conda.runConda([`gz-sim${version}`]); + } + }); +} + + /***/ }), /***/ 525: @@ -26568,6 +26712,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); const linux = __importStar(__nccwpck_require__(5467)); const macOs = __importStar(__nccwpck_require__(8247)); +const windows = __importStar(__nccwpck_require__(8502)); function run() { return __awaiter(this, void 0, void 0, function* () { try { @@ -26578,6 +26723,9 @@ function run() { else if (platform === "linux") { linux.runLinux(); } + else if (platform === "win32") { + windows.runWindows(); + } else { throw new Error(`Unsupported platform ${platform}`); } diff --git a/src/package_manager/conda.ts b/src/package_manager/conda.ts new file mode 100644 index 0000000..e60776a --- /dev/null +++ b/src/package_manager/conda.ts @@ -0,0 +1,14 @@ +import * as utils from "../utils"; + +/** + * Run conda install on a list of specified packages. + * + * @param packages list of conda-forge packages to be installed + * @returns Promise exit code + */ +export async function runConda(packages: string[]): Promise { + return utils.exec( + "conda", + ["install", "--channel", "conda-forge"].concat(packages), + ); +} diff --git a/src/setup-gazebo-windows.ts b/src/setup-gazebo-windows.ts new file mode 100644 index 0000000..2059b7b --- /dev/null +++ b/src/setup-gazebo-windows.ts @@ -0,0 +1,45 @@ +import * as utils from "./utils"; +import * as conda from "./package_manager/conda"; + +// List of mapped Gazebo distro to gz-sim versions +const validLibVersions: { distro: string; libVersion: number }[] = [ + { + distro: "garden", + libVersion: 7, + }, + { + distro: "harmonic", + libVersion: 8, + }, +]; + +/** + * Get gz-sim library version corresponding to a gz-$collection + * since conda does not currently support the gz-$collection metapackages. + * See https://github.com/conda-forge/gz-sim-feedstock/issues/61 + * + * @param gazeboDistro name of Gazebo distribution + * @returns gz-sim version + */ +async function getLibVersion(gazeboDistro: string): Promise { + let version: number | undefined; + validLibVersions.forEach((obj) => { + if (obj.distro == gazeboDistro) { + version = obj.libVersion; + } + }); + if (version === undefined) { + throw new Error(`No conda packages available for gz-${gazeboDistro}`); + } + return version; +} + +/** + * Install Gazebo on a Windows worker + */ +export async function runWindows(): Promise { + for (const gazeboDistro of utils.getRequiredGazeboDistributions()) { + const version = await getLibVersion(gazeboDistro); + await conda.runConda([`gz-sim${version}`]); + } +} diff --git a/src/setup-gazebo.ts b/src/setup-gazebo.ts index 288d6c1..798f326 100644 --- a/src/setup-gazebo.ts +++ b/src/setup-gazebo.ts @@ -1,6 +1,7 @@ import * as core from "@actions/core"; import * as linux from "./setup-gazebo-linux"; import * as macOs from "./setup-gazebo-macos"; +import * as windows from "./setup-gazebo-windows"; async function run() { try { @@ -9,6 +10,8 @@ async function run() { macOs.runMacOs(); } else if (platform === "linux") { linux.runLinux(); + } else if (platform === "win32") { + windows.runWindows(); } else { throw new Error(`Unsupported platform ${platform}`); }