diff --git a/README.md b/README.md index 24d338017..12793a6ca 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ It also performs the following actions: - On macOS: - Installing ROS and ROS 2 system dependencies using [Homebrew] and [pip] - On Microsoft Windows: - - Installing ROS and ROS 2 system dependencies using [Chocolatey] + - Installing ROS 2 binaries and their dependencies using [pixi] The dependencies installed by this package include ROS 2 DDS vendor packages, such as Eclipse Cyclone DDS, eProsima Fast DDS, and RTI Connext DDS. See [`src/package_manager/*.ts`](./src/package_manager/) for the complete list. @@ -294,7 +294,7 @@ The scripts and documentation in this project are released under the [Apache 2]( [ros2_latest_development_setup]: https://docs.ros.org/en/rolling/Installation/Alternatives/Latest-Development-Setup.html [rep-2000]: https://www.ros.org/reps/rep-2000.html [rep-3]: https://www.ros.org/reps/rep-0003.html -[chocolatey]: https://chocolatey.org/ +[pixi]: https://pixi.sh/ [homebrew]: https://brew.sh/ [pip]: https://pip.pypa.io/en/stable/ [dockerhub_osrf]: https://hub.docker.com/r/osrf/ros/ diff --git a/src/package_manager/chocolatey.ts b/src/package_manager/chocolatey.ts deleted file mode 100644 index 77deffe05..000000000 --- a/src/package_manager/chocolatey.ts +++ /dev/null @@ -1,95 +0,0 @@ -import * as utils from "../utils"; - -const chocoCommandLine: string[] = [ - "install", - "--limit-output", - "--no-progress", - "--yes", -]; - -const chocoDependencies: string[] = ["wget", "7zip", "lcov"]; - -const chocoDependenciesPinned: [string, string][] = [ - ["cppcheck", "1.90"], - ["openssl", "1.1.1.2100"], -]; - -const ros2ChocolateyPackagesUrl: string[] = [ - "https://github.com/ros2/choco-packages/releases/download/2022-03-15/asio.1.12.1.nupkg", - "https://github.com/ros2/choco-packages/releases/download/2022-03-15/bullet.3.17.nupkg", - "https://github.com/ros2/choco-packages/releases/download/2022-03-15/cunit.2.1.3.nupkg", - "https://github.com/ros2/choco-packages/releases/download/2022-03-15/eigen.3.3.4.nupkg", - "https://github.com/ros2/choco-packages/releases/download/2022-03-15/log4cxx.0.10.0.nupkg", - "https://github.com/ros2/choco-packages/releases/download/2022-03-15/tinyxml-usestl.2.6.2.nupkg", - "https://github.com/ros2/choco-packages/releases/download/2022-03-15/tinyxml2.6.0.0.nupkg", -]; -const ros2ChocolateyPackages: string[] = [ - "asio", - "bullet", - "cunit", - "eigen", - "log4cxx", - "tinyxml-usestl", - "tinyxml2", -]; - -/** - * Run choco install on the list of specified packages. - * - * @param packages list of Chocolatey pacakges to be installed - * @returns Promise exit code - */ -export async function runChocoInstall(packages: string[]): Promise { - return utils.exec("choco", chocoCommandLine.concat(packages)); -} - -/** - * Run choco install on the list of specified packages and versions. - * - * @param packages list of Chocolatey pacakges to be installed along with their versions - * @returns Promise exit code - */ -export async function runChocoInstallVersion( - packages: [string, string][], -): Promise { - let ret = 0; - for (const [pkg, version] of packages) { - ret += await utils.exec( - "choco", - chocoCommandLine.concat(pkg, "--version", version), - ); - } - return ret !== 0 ? 1 : 0; -} - -/** - * Install ROS 2 Chocolatey dependencies. - * - * @returns Promise exit code - */ -export async function installChocoDependencies(): Promise { - return ( - (await runChocoInstall(chocoDependencies)) + - (await runChocoInstallVersion(chocoDependenciesPinned)) - ); -} - -/** - * Download Open Robotics maintained packages from GitHub and install them. - * - * @returns Promise exit code - */ -export async function downloadAndInstallRos2NugetPackages(): Promise { - await utils.exec("wget", ["--quiet"].concat(ros2ChocolateyPackagesUrl)); - // The ROS 2 NUGET Chocolatey packages expect a registry entry of - // HKCU\SOFTWARE\Kitware\CMake to exist; if it doesn't, they don't - // properly register themselves with CMake and thus downstream software - // can't properly find them. The Windows image that is currently available - // to GitHub actions (https://github.com/actions/virtual-environments/blob/win19/20200608.1/images/win/Windows2019-Readme.md) - // doesn't seem to have this key, so add it by hand here. - await utils.exec("reg", ["add", "HKCU\\SOFTWARE\\Kitware\\CMake", "/f"]); - return utils.exec( - "choco", - chocoCommandLine.concat("--source", ".").concat(ros2ChocolateyPackages), - ); -} diff --git a/src/setup-ros-windows.ts b/src/setup-ros-windows.ts index 09e56c205..5f48c4ed2 100644 --- a/src/setup-ros-windows.ts +++ b/src/setup-ros-windows.ts @@ -1,92 +1,156 @@ import * as core from "@actions/core"; import * as tc from "@actions/tool-cache"; +import * as fs from "fs"; +import * as os from "os"; import * as path from "path"; -import * as chocolatey from "./package_manager/chocolatey"; -import * as pip from "./package_manager/pip"; import * as utils from "./utils"; +// Pixi-based binary releases; humble and iron are not supported. const binaryReleases: { [index: string]: string } = { - humble: - "https://github.com/ros2/ros2/releases/download/humble-20250331/ros2-humble-20250331-windows-release-amd64.zip", - iron: "https://github.com/ros2/ros2/releases/download/release-iron-20241204/ros2-iron-20241204-windows-release-amd64.zip", jazzy: "https://github.com/ros2/ros2/releases/download/release-jazzy-20250430/ros2-jazzy-20250407-windows-release-amd64.zip", kilted: "https://github.com/ros2/ros2/releases/download/release-kilted-20250523/ros2-kilted-20250523-windows-release-amd64.zip", lyrical: "https://github.com/ros2/ros2/releases/download/release-lyrical-beta-20260430/ros2-lyrical-2026-04-30-windows-AMD64.zip", + rolling: + "https://github.com/ros2/ros2/releases/download/release-rolling-nightlies/ros2-rolling-nightly-windows-amd64.zip", }; -const pip3Packages: string[] = ["lxml", "netifaces"]; +const pixiWorkspace = "C:\\pixi_ws"; -/** - * Install ROS 2 build tools. - */ -async function prepareRos2BuildEnvironment() { - // Currently targeted Python version for Windows according to REP 2000 - const python_dir = tc.find("Python", "3.8"); +// Windows 11 starts at build 22000; GitHub-hosted runners are Windows Server and will warn. +function checkWindows11() { + const build = Number(os.release().split(".")[2]); + if (!(build >= 22000)) { + core.warning(`ROS 2 binaries only support Windows 11 (found ${os.release()})`); + } +} + +async function findVs2022(): Promise { + const vswhere = path.join( + process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)", + "Microsoft Visual Studio", + "Installer", + "vswhere.exe", + ); + if (!fs.existsSync(vswhere)) { + return ""; + } + let installPath = ""; await utils.exec( - path.join(python_dir, "python"), + vswhere, [ - "-c", - "import sysconfig; print(sysconfig.get_config_var('BINDIR')); print(sysconfig.get_path('scripts'))", + "-latest", + "-products", + "*", + "-version", + "[17.0,18.0)", + "-requires", + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "-property", + "installationPath", ], - { - listeners: { - stdline: (data: string) => { - const p = data.trim(); - if (p) { - core.info("Prepending to path: " + JSON.stringify(p)); - core.addPath(p); - } - }, - }, - }, + { listeners: { stdout: (d: Buffer) => (installPath += d.toString()) } }, ); + return installPath.trim(); +} - core.addPath("c:\\program files\\cppcheck"); - await chocolatey.installChocoDependencies(); - await chocolatey.downloadAndInstallRos2NugetPackages(); +/** + * Activate the Visual Studio 2022 (MSVC v143) toolchain, installing the Build Tools if missing. + * VS 18+ is not supported by ROS 2 yet. + */ +async function selectMsvc2022() { + let installPath = await findVs2022(); + if (!installPath) { + const bootstrapper = await tc.downloadTool( + "https://aka.ms/vs/17/release/vs_BuildTools.exe", + path.join(process.env["RUNNER_TEMP"] || os.tmpdir(), "vs_BuildTools.exe"), + ); + // 3010 means success, reboot pending. + const code = await utils.exec( + bootstrapper, + [ + "--quiet", + "--wait", + "--norestart", + "--nocache", + "--add", + "Microsoft.VisualStudio.Workload.VCTools", + "--includeRecommended", + ], + { ignoreReturnCode: true }, + ); + if (code && code !== 3010) { + throw new Error(`VS 2022 Build Tools installation failed with exit code ${code}`); + } + installPath = await findVs2022(); + } + if (!installPath) { + core.warning("Visual Studio 2022 C++ tools not found, skipping MSVC selection"); + return; + } - // Avoid version of pip that breaks Windows GitHub actions. See: - // * https://github.com/ros-tooling/action-ros-ci/pull/719#issuecomment-1030318146 - // * https://github.com/actions/virtual-environments/issues/5027#issuecomment-1031113617 - await utils.exec("python", ["-m", "pip", "install", "-U", "pip!=22.0.*"], { - cwd: path.sep, + const vcvars = path.join(installPath, "VC", "Auxiliary", "Build", "vcvars64.bat"); + let envDump = ""; + await utils.exec("cmd", ["/d", "/s", "/c", `""${vcvars}" >nul && set"`], { + windowsVerbatimArguments: true, + listeners: { stdout: (d: Buffer) => (envDump += d.toString()) }, }); + for (const line of envDump.split(/\r?\n/)) { + const i = line.indexOf("="); + if (i > 0 && process.env[line.slice(0, i)] !== line.slice(i + 1)) { + core.exportVariable(line.slice(0, i), line.slice(i + 1)); + } + } +} - await pip.installPython3Dependencies(false); - await pip.runPython3PipInstall(pip3Packages, false); - await pip.runPython3PipInstall(["vcstool"], false); - // install "rosdistro" from master to include distutils fix: - // https://github.com/ros-infrastructure/rosdistro/pull/194 - await pip.runPython3PipInstall( - ["git+https://github.com/ros-infrastructure/rosdistro.git"], - false, - ); - await pip.runPython3PipInstall(["rosdep"], false); - return utils.exec(`rosdep`, ["init"]); +async function installPixi() { + await utils.exec("powershell", [ + "-ExecutionPolicy", + "ByPass", + "-Command", + "irm -useb https://pixi.sh/install.ps1 | iex", + ]); + core.addPath(path.join(os.homedir(), ".pixi", "bin")); } /** - * Install ROS 2 binary releases. + * Install ROS 2 binary releases and their pixi environments. */ async function prepareRos2BinaryReleases() { for (const rosDistro of utils.getRequiredRosDistributions()) { - if (rosDistro in binaryReleases) { - await utils.exec("wget", [ - "--quiet", - binaryReleases[rosDistro], - "-O", - `${rosDistro}.zip`, - ]); - await utils.exec("7z", [ - "x", - `${rosDistro}.zip`, - "-y", - `-oC:\\dev\\${rosDistro}`, + if (!(rosDistro in binaryReleases)) { + core.warning(`No Windows binary release available for ${rosDistro}`); + continue; + } + const installDir = path.join(pixiWorkspace, rosDistro); + const manifestPath = path.join(installDir, "pixi.toml"); + fs.mkdirSync(installDir, { recursive: true }); + + await tc.downloadTool( + `https://raw.githubusercontent.com/ros2/ros2/refs/heads/${rosDistro}/pixi.toml`, + manifestPath, + ); + const zipPath = await tc.downloadTool(binaryReleases[rosDistro]); + await tc.extractZip(zipPath, installDir); + + await utils.exec("pixi", ["install", "--manifest-path", manifestPath]); + + const preinstallScript = path.join( + installDir, + "ros2-windows", + "preinstall_setup_windows.py", + ); + if (fs.existsSync(preinstallScript)) { + await utils.exec("pixi", [ + "run", + "--manifest-path", + manifestPath, + "python", + preinstallScript, ]); } } @@ -96,6 +160,8 @@ async function prepareRos2BinaryReleases() { * Install build environment on a Windows worker. */ export async function runWindows() { - await prepareRos2BuildEnvironment(); + checkWindows11(); + await selectMsvc2022(); + await installPixi(); return await prepareRos2BinaryReleases(); }