diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b5cb384..f3d7f66 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,13 +18,42 @@ jobs: uses: actions/checkout@v2 - name: Download Internet run: npm install - - name: Enable Developer Command Prompt + - name: Enable Developer Command Prompt (amd64) uses: ./ - - name: Compile and run some C code + with: + arch: amd64 + - name: Compile and run some C code (amd64) shell: cmd run: | cl.exe hello.c hello.exe + - name: Enable Developer Command Prompt (amd64_x86) + uses: ./ + with: + arch: amd64_x86 + - name: Compile and run some C code (x86) + shell: cmd + run: | + cl.exe hello.c + hello.exe + - name: Enable Developer Command Prompt (amd64_arm) + uses: ./ + with: + arch: amd64_arm + - name: Compile some C code (arm) + shell: cmd + run: | + cl.exe hello.c + dumpbin /headers hello.exe + - name: Enable Developer Command Prompt (amd64_arm64) + uses: ./ + with: + arch: amd64_arm64 + - name: Compile some C code (arm64) + shell: cmd + run: | + cl.exe hello.c + dumpbin /headers hello.exe audit: name: npm audit runs-on: windows-latest diff --git a/README.md b/README.md index 71b3128..a68b89e 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,57 @@ This sets up the environment for compiling C/C++ code from command line. Supports Windows. Does nothing on Linux and macOS. +## Example usage + +Basic usage for default compilation settings is like this: + +```yaml +jobs: + test: + steps: + - uses: actions/checkout@v2 + - uses: ilammy/msvc-dev-cmd@v1 + - name: Build something requiring CL.EXE + run: | + cmake -G "NMake Makefiles" . + nmake + # ... +``` + +If you want something non-default, +like using a specific version of Visual Studio, +or cross-compling for a differen target, +you will need to configure those settings via inputs: + +```yaml +jobs: + test: + # Run a job for each of the specified target architectures: + strategy: + matrix: + arch: + - amd64 + - amd64_x86 + - amd64_arm64 + steps: + - uses: actions/checkout@v2 + - uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.arch }} + - name: Build something requiring CL.EXE + run: | + cmake -G "NMake Makefiles" . + nmake + # ... +``` + ## Inputs - `arch` – target architecture - native compilation: - `x64` (default) or its synonyms: `amd64`, `win64` - `x86` or its synonyms: `win32` - - cross-compilation: `x86_amd64`, `x86_arm`, `x86_arm64`, - `amd64_x86`, `amd64_arm`, `amd64_arm64` + - cross-compilation: `x86_amd64`, `x86_arm`, `x86_arm64`, `amd64_x86`, `amd64_arm`, `amd64_arm64` - `sdk` – Windows SDK to use - do not specify to use the default SDK - or specify full Windows 10 SDK number (e.g, `10.0.10240.0`) @@ -28,20 +71,64 @@ Supports Windows. Does nothing on Linux and macOS. - `uwp` – set `true` to build for Universal Windows Platform (i.e., for Windows Store) - `spectre` – set `true` to use Visual Studio libraries with [Spectre](https://meltdownattack.com) mitigations -## Example usage +## Caveats + +### Name conflicts with `shell: bash` + +Using `shell: bash` in Actions may shadow some of the paths added by MSVC. +In particular, `link.exe` (Microsoft C linker) is prone to be shadowed by `/usr/bin/link` (GNU filesystem link tool). + +Unfortunately, this happens because GitHub Actions unconditionally *prepend* GNU paths when `shell: bash` is used, +on top of any paths set by `msvc-dev-cmd`, every time at the start of each new step. +Hence, there aren't many non-destructive options here. + +If you experience compilation errors where `link` complains about unreasonable command-line arguments, +“extra operand *something-something*” – that's probably it. +Recommended workaround is to remove `/usr/bin/link` if that interferes with your builds. +If this is not acceptable, please file an issue, then we'll figure out something better. + +### Reconfiguration + +You can invoke `ilammy/msvc-dev-cmd` multiple times during your jobs with different inputs +to reconfigure the environment for building with different settings +(e.g., to target multiple architectures). ```yaml jobs: - test: - - uses: actions/checkout@v1 - - uses: ilammy/msvc-dev-cmd@v1 - - name: Build something requiring CL.EXE - run: | - cmake -G "NMake Makefiles" . - nmake - # ... + release: + steps: + # ... + - name: Configure build for amd64 + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: amd64 + + - run: build # (for amd64) + + - name: Configure build for x86 + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: amd64_x86 + + - run: build # (for x86) + + - name: Configure build for ARM64 + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: amd64_arm64 + + - run: build # (for ARM64) + + # ... ``` +This mostly works but it's not really recommended +since Developer Command Prompt was not meant for recursive reconfiguration. +That said, if it does not work for you, please file an issue. + +Consider using [`strategy.matrix`](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix) +to execute different build configuration in parallel, independent environments. + ## License MIT, see [LICENSE](LICENSE). diff --git a/index.js b/index.js index 1b78b02..c87d7e0 100644 --- a/index.js +++ b/index.js @@ -11,21 +11,6 @@ const VERSIONS = ['2019', '2017'] const VSWHERE_PATH = `${PROGRAM_FILES_X86}\\Microsoft Visual Studio\\Installer` -const InterestingVariables = [ - 'INCLUDE', - 'LIB', - 'LIBPATH', - 'VCINSTALLDIR', - 'Path', - 'Platform', - 'VisualStudioVersion', - 'UCRTVersion', - 'UniversalCRTSdkDir', - /^VCTools/, - /^VSCMD_/, - /^WindowsSDK/i, -] - function findWithVswhere(pattern) { try { let installationPath = child_process.execSync(`vswhere -products * -latest -prerelease -property installationPath`).toString().trim() @@ -70,6 +55,21 @@ function findVcvarsall() { throw new Error('Microsoft Visual Studio not found') } +function isPathVariable(name) { + const pathLikeVariables = ['PATH', 'INCLUDE', 'LIB', 'LIBPATH'] + return pathLikeVariables.indexOf(name.toUpperCase()) != -1 +} + +function filterPathValue(path) { + let paths = path.split(';') + // Remove duplicates by keeping the first occurance and preserving order. + // This keeps path shadowing working as intended. + function unique(value, index, self) { + return self.indexOf(value) === index + } + return paths.filter(unique).join(';') +} + function main() { if (process.platform != 'win32') { core.info('This is not a Windows virtual environment, bye!') @@ -113,15 +113,17 @@ function main() { args.push('-vcvars_spectre_libs=spectre') } - const command = `"${findVcvarsall()}" ${args.join(' ')} && set` - core.debug(`Running: ${command}`) - const environment = child_process.execSync(command, {shell: "cmd"}).toString().split('\r\n') + const vcvars = `"${findVcvarsall()}" ${args.join(' ')}` + core.debug(`vcvars command-line: ${vcvars}`) + + const old_environment = child_process.execSync(`set`, {shell: "cmd"}).toString().split('\r\n') + const new_environment = child_process.execSync(`${vcvars} && set`, {shell: "cmd"}).toString().split('\r\n') // If vsvars.bat is given an incorrect command line, it will print out // an error and *still* exit successfully. Parse out errors from output // which don't look like environment variables, and fail if appropriate. var failed = false - for (let line of environment) { + for (let line of new_environment) { if (line.match(/^\[ERROR.*\]/)) { failed = true // Don't print this particular line which will be confusing in output. @@ -135,15 +137,39 @@ function main() { throw new Error('invalid parameters') } - for (let string of environment) { + // Convert old environment lines into a dictionary for easier lookup. + let old_env_vars = {} + for (let string of old_environment) { const [name, value] = string.split('=') - for (let pattern of InterestingVariables) { - if (name.match(pattern)) { - core.exportVariable(name, value) - break + old_env_vars[name] = value + } + + // Now look at the new environment and export everything that changed. + // These are the variables set by vsvars.bat. Also export everything + // that was not there during the first sweep: those are new variables. + core.startGroup('Environment variables') + for (let string of new_environment) { + // vsvars.bat likes to print some fluff at the beginning. + // Skip lines that don't look like environment variables. + if (!string.includes('=')) { + continue; + } + let [name, new_value] = string.split('=') + let old_value = old_env_vars[name] + // For new variables "old_value === undefined". + if (new_value !== old_value) { + core.info(`Setting ${name}`) + // Special case for a bunch of PATH-like variables: vcvarsall.bat + // just prepends its stuff without checking if its already there. + // This makes repeated invocations of this action fail after some + // point, when the environment variable overflows. Avoid that. + if (isPathVariable(name)) { + new_value = filterPathValue(new_value) } + core.exportVariable(name, new_value) } } + core.endGroup() core.info(`Configured Developer Command Prompt`) } diff --git a/package-lock.json b/package-lock.json index 40a74c7..cb1cb1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "msvc-dev-cmd", - "version": "1.6.0", + "version": "1.8.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 5e14d1d..9a5945b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "msvc-dev-cmd", - "version": "1.6.0", + "version": "1.8.0", "description": "GitHub Action to setup Developer Command Prompt for Microsoft Visual C++", "main": "index.js", "scripts": {