Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,25 @@ This project uses `esbuild` to compile the JavaScript source code for each actio
## Available Actions

1. **`build-docker-image`**
* Builds a Docker image, with optional caching and pushing via Azure Container Registry.
* Builds a Docker image, with optional caching and pushing via Azure Container Registry. This Action is Linux only.
* See: [`actions/build-docker-image/action.yml`](./actions/build-docker-image/action.yml)
* *(Optional: Add link to a more detailed README if it exists at `actions/build-docker-image/README.md`)*
* See details: [`.github/actions/build-docker-image/README.md`](./.github/actions/build-docker-image/README.md)

2. **`run-build-script-in-docker`**
* Runs the ONNX Runtime `tools/ci_build/build.py` script inside a specified Docker container.
* Runs the ONNX Runtime `tools/ci_build/build.py` script inside a specified Docker container. This Action is Linux only.
* Supports different modes (`update`, `build`, `test`).
* Includes auto-detection for NVIDIA GPUs (`--gpus all`).
* Manages common volume mounts (workspace, cache, test data).
* Handles enabling execution providers via `--use_<ep>` flags.
* See: [`actions/run-build-script-in-docker/action.yml`](./actions/run-build-script-in-docker/action.yml)

3. **`setup-vcpkg`**
* Downloads, verifies, bootstraps, and caches a specific tagged version of vcpkg. This Action is Windows only.
* Sets the `VCPKG_INSTALLATION_ROOT` environment variable for subsequent steps.
* Leverages `@actions/tool-cache` for efficient caching.
* See: [`.github/actions/setup-vcpkg/action.yml`](./.github/actions/setup-vcpkg/action.yml)
* See details: [`.github/actions/setup-vcpkg/README.md`](./.github/actions/setup-vcpkg/README.md)

## Usage (for Consumers)

Because the compiled action code (in the `build/` directory) is not present on the `main` branch or directly associated with version tags in the repository filesystem, you **cannot** use the actions directly like this:
Expand Down
73 changes: 73 additions & 0 deletions actions/setup-vcpkg/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Setup vcpkg GitHub Action

This action downloads a specific tagged release archive of vcpkg from an internal mirror, verifies its SHA512 hash for integrity, extracts it, runs the necessary vcpkg bootstrap script (`bootstrap-vcpkg.bat` or `bootstrap-vcpkg.sh`), and caches the resulting installation using `@actions/tool-cache`. Currently this action only runs on Windows.

It simplifies the process of getting a ready-to-use vcpkg instance in your GitHub Actions workflows, ensuring consistency and leveraging caching for speed.

Once set up, it exports the `VCPKG_INSTALLATION_ROOT` environment variable pointing to the cached installation path, which can be used by subsequent steps (e.g., CMake toolchain configuration). ONNX Runtime's [build.py](https://github.com/microsoft/onnxruntime/blob/main/tools/ci_build/build.py) reads this environment variable.

## Inputs

| Input | Description | Required | Default | Example |
| -------------------- | ---------------------------------------------------------------- | :------: | --------------------------------------------- | -------------- |
| `vcpkg-version` | The vcpkg tag version to download (e.g., `2023.10.19`) | `true` | - | `2025.03.19` |
| `vcpkg-hash` | The expected SHA512 hash (used by Terrapin tool via `-s`). | `true` | - | `17e96169...` |
| `terrapin-tool-path` | Path to the `TerrapinRetrievalTool.exe` executable. | `true` | `C:/local/Terrapin/TerrapinRetrievalTool.exe` | |

**Finding Hashes:** You typically need to download the specific tag's zip archive (e.g., `https://github.com/microsoft/vcpkg/archive/refs/tags/YYYY.MM.DD.zip`) and calculate its SHA512 hash locally.

## Outputs

| Output | Description | Example Usage |
| ------------ | ---------------------------------------------------- | ------------------------------------------ |
| `vcpkg-root` | The absolute path to the cached vcpkg installation. | `${{ steps.setup_vcpkg.outputs.vcpkg-root }}` |

## Environment Variables

This action sets the following environment variable for subsequent steps in the job:

* **`VCPKG_INSTALLATION_ROOT`**: The absolute path to the cached vcpkg installation directory (same value as the `vcpkg-root` output). This is commonly used by build systems like CMake (`-DCMAKE_TOOLCHAIN_FILE=$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake`).

## Caching

* The action utilizes `@actions/tool-cache` for caching.
* The cache key is based on the tool name (`vcpkg`), the specified `vcpkg-version`, and the runner's architecture/OS.
* It caches the **bootstrapped** version of vcpkg, meaning the `vcpkg` executable should be present and ready to use within the cached directory. Subsequent runs hitting the cache will be significantly faster as they skip download, verification, extraction, and bootstrapping.

## Example Usage

```yaml
name: Build with vcpkg

on: [push]

jobs:
build:
runs-on: windows-latest # Or ubuntu-latest, macos-latest

steps:
- uses: actions/checkout@v4

- name: Setup vcpkg
id: setup_vcpkg # Give it an ID to access outputs
# Assuming the action is in the same repository
uses: ./.github/actions/setup-vcpkg
with:
# Find specific versions and hashes on the vcpkg releases page or repo
vcpkg-version: '2025.03.19' # Replace with desired tag
vcpkg-hash: '17e96169cd3f266c4716fcdc1bb728e6a64f103941ece463a2834d50694eba4fb48f30135503fd466402afa139abc847ef630733c442595d1c34979f261b0114' # Replace with the correct SHA512 hash for the version

- name: Use vcpkg (CMake Example)
shell: bash # Or pwsh, cmd
run: |
echo "Vcpkg is installed at: $VCPKG_INSTALLATION_ROOT"
echo "Vcpkg root output: ${{ steps.setup_vcpkg.outputs.vcpkg-root }}"

# Example CMake configuration using the VCPKG_INSTALLATION_ROOT env var
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=<span class="math-inline">VCPKG\_INSTALLATION\_ROOT/scripts/buildsystems/vcpkg\.cmake
\# Or using the output directly \(less common for env vars\)
\# cmake \-B build \-S \. \-DCMAKE\_TOOLCHAIN\_FILE\=</span>{{ steps.setup_vcpkg.outputs.vcpkg-root }}/scripts/buildsystems/vcpkg.cmake

cmake --build build

```
22 changes: 22 additions & 0 deletions actions/setup-vcpkg/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: 'Setup vcpkg'
description: 'Downloads, verifies, extracts, and caches a specific tagged version of vcpkg'

inputs:
vcpkg-version:
description: 'The vcpkg tag version to download (e.g., 2025.03.19)'
required: true
vcpkg-hash:
description: 'The expected SHA512 hash (used by Terrapin tool via -s)'
required: true
terrapin-tool-path:
description: 'Path to the TerrapinRetrievalTool.exe executable'
required: false
default: 'C:/local/Terrapin/TerrapinRetrievalTool.exe'

outputs:
vcpkg-root:
description: 'The installation path of the cached vcpkg instance'

runs:
using: 'node20'
main: 'dist/index.js'
19 changes: 16 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
"author": "",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/core": "^1.11.1",
"@actions/exec": "^1.1.1",
"@actions/github": "^6.0.0"
"@actions/github": "^6.0.0",
"@actions/tool-cache": "^2.0.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
Expand Down
158 changes: 158 additions & 0 deletions src/setup-vcpkg/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
const core = require('@actions/core');
const tc = require('@actions/tool-cache');
const exec = require('@actions/exec');
const crypto = require('node:crypto');
const path = require('node:path');
const fs = require('node:fs');

/**
* Verifies the SHA512 hash of a downloaded file.
* @param {string} filePath - Path to the file to verify.
* @param {string} expectedHash - The expected SHA512 hash in hex format.
* @returns {Promise<boolean>} - True if the hash matches, false otherwise.
*/
async function verifySHA512(filePath, expectedHash) {
return new Promise((resolve, reject) => {
core.info(`Calculating SHA512 for file: ${filePath}`);
const hash = crypto.createHash('sha512');
const stream = fs.createReadStream(filePath);
stream.on('error', err => reject(err));
stream.on('data', chunk => hash.update(chunk));
stream.on('end', () => {
const actualHash = hash.digest('hex');
core.info(`Actual SHA512: ${actualHash}`);
core.info(`Expected SHA512: ${expectedHash}`);
resolve(actualHash.toLowerCase() === expectedHash.toLowerCase());
});
});
}

async function run() {
try {
// --- Get Inputs ---
const vcpkgVersion = core.getInput('vcpkg-version', { required: true });
const vcpkgHash = core.getInput('vcpkg-hash', { required: true });
const terrapinPath = core.getInput('terrapin-tool-path');

const toolName = 'vcpkg';
const extractedFolderName = `vcpkg-${vcpkgVersion}`;

core.info(`Setting up vcpkg version: ${vcpkgVersion}`);
core.info(`Using Terrapin Tool at: ${terrapinPath}`);

// --- Cache Check ---
let vcpkgPath = tc.find(toolName, vcpkgVersion);

if (vcpkgPath) {
core.info(`Found cached vcpkg at: ${vcpkgPath}`);
} else {
// --- Cache Miss ---
core.info(`vcpkg version ${vcpkgVersion} not found in cache. Downloading via Terrapin Tool...`);

// --- Determine Download Destination ---
const runnerTempDir = process.env.RUNNER_TEMP;
if (!runnerTempDir) {
throw new Error('RUNNER_TEMP environment variable is not defined.');
}
const vcpkgZipPath = path.join(runnerTempDir, 'vcpkg.zip');
core.info(`Terrapin will download to: ${vcpkgZipPath}`);

// --- Execute Terrapin Download ---
// Note: Terrapin might *also* verify the hash via -s argument.
const terrapinBaseUrl = 'https://vcpkg.storage.devpackages.microsoft.io/artifacts/';
const terrapinPackageUrl = `https://github.com/microsoft/vcpkg/archive/refs/tags/${vcpkgVersion}.zip`;
const terrapinArgs = [
'-b', terrapinBaseUrl,
'-a', 'true',
'-u', 'Environment',
'-p', terrapinPackageUrl,
'-s', vcpkgHash, // Hash for verification by Terrapin
'-d', vcpkgZipPath
];

await core.group('Downloading vcpkg via Terrapin Tool', async () => {
await exec.exec(`"${terrapinPath}"`, terrapinArgs);
});
core.info(`Download via Terrapin completed.`);

// --- Verify Download Happened ---
if (!fs.existsSync(vcpkgZipPath)) {
throw new Error(`Download failed: Expected file not found at ${vcpkgZipPath} after Terrapin execution.`);
}

// --- Verify SHA512 Hash (Manual Check) ---
core.info('Verifying SHA512 hash (manual check)...');
const hashMatch = await verifySHA512(vcpkgZipPath, vcpkgHash);
if (!hashMatch) {
// Use core.setFailed for clear failure indication in GitHub Actions UI
core.setFailed('SHA512 hash verification failed! Downloaded file hash does not match expected hash.');
return; // Stop execution if hash fails
}
core.info('Manual hash verification successful.');
// --- End Manual Hash Check ---


// --- Extract ---
core.info(`Extracting ${vcpkgZipPath}...`);
const tempExtractPath = await tc.extractZip(vcpkgZipPath);
core.info(`Extracted vcpkg to temporary location: ${tempExtractPath}`);

let extractedPath = path.join(tempExtractPath, extractedFolderName);
if (!fs.existsSync(extractedPath)) {
const files = fs.readdirSync(tempExtractPath);
core.warning(`Expected folder '${extractedFolderName}' not found directly. Contents: ${files.join(', ')}`);
if (files.length === 1 && fs.statSync(path.join(tempExtractPath, files[0])).isDirectory()) {
core.warning(`Assuming first directory '${files[0]}' is the correct one.`);
extractedPath = path.join(tempExtractPath, files[0]);
} else {
throw new Error(`Could not find the extracted vcpkg directory inside ${tempExtractPath}. Expected name pattern: ${extractedFolderName}`);
}
}

// --- Bootstrap vcpkg ---
core.info(`Bootstrapping vcpkg in ${extractedPath}...`);
const bootstrapScriptName = process.platform === 'win32' ? 'bootstrap-vcpkg.bat' : 'bootstrap-vcpkg.sh';
const bootstrapScriptPath = path.join(extractedPath, bootstrapScriptName);
const bootstrapArgs = ['-disableMetrics'];

if (!fs.existsSync(bootstrapScriptPath)) {
throw new Error(`Bootstrap script not found at ${bootstrapScriptPath}`);
}

await core.group('Running vcpkg bootstrap', async () => {
const options = { cwd: extractedPath };
if (process.platform !== 'win32') {
await exec.exec('chmod', ['+x', bootstrapScriptPath]);
}
core.info(`Executing: ${bootstrapScriptPath} ${bootstrapArgs.join(' ')}`);
await exec.exec(bootstrapScriptPath, bootstrapArgs, options);
});
core.info('vcpkg bootstrapped successfully.');

// --- Cache Directory ---
core.info(`Caching bootstrapped directory: ${extractedPath}`);
vcpkgPath = await tc.cacheDir(extractedPath, toolName, vcpkgVersion);
core.info(`Successfully cached vcpkg to: ${vcpkgPath}`);
}

// --- Set Environment Variable & Output ---
core.info(`Setting VCPKG_INSTALLATION_ROOT to ${vcpkgPath}`);
core.exportVariable('VCPKG_INSTALLATION_ROOT', vcpkgPath);
core.setOutput('vcpkg-root', vcpkgPath);

core.info('vcpkg setup complete.');

} catch (error) {
// Catch errors from exec, hash check failure (via setFailed), etc.
// If core.setFailed was already called, this will likely just reiterate.
// If an exception was thrown (e.g., download failed, file not found), this catches it.
core.setFailed(error.message);
}
}

if (require.main === module) {
run();
}

// Always export run for testing or other programmatic usage
module.exports = { run };