Skip to content

Commit

Permalink
msvc-dev-cmd v1.8.0
Browse files Browse the repository at this point in the history
  • Loading branch information
ilammy committed May 8, 2021
2 parents 18f5f2c + 08b850b commit 5611a7c
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 39 deletions.
33 changes: 31 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
109 changes: 98 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand All @@ -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).
74 changes: 50 additions & 24 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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!')
Expand Down Expand Up @@ -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.
Expand All @@ -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`)
}
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down

0 comments on commit 5611a7c

Please sign in to comment.