├── .github
├── ado
│ └── azure-pipeline.yml
└── workflows
│ └── run-tests.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── SECURITY.md
├── SUPPORT.md
├── action.yml
├── dist
├── index.js
├── licenses.txt
└── vc_env.bat
├── index.js
├── package-lock.json
├── package.json
└── test
├── cmake-api-tests.js
└── sample
├── CMakeLists.txt
├── Custom.ruleset
├── Invalid.ruleset
├── failure
├── CMakeLists.txt
└── fail.cpp
├── include
├── regular
│ └── regular.h
└── system
│ └── system.h
└── src
├── fileA.cpp
└── fileB.cpp
/.github/ado/azure-pipeline.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - main
3 |
4 | pool:
5 | vmImage: ubuntu-latest
6 |
7 | variables:
8 | - name: Codeql.Enabled
9 | value: true
10 | - name: Codeql.TSAEnabled
11 | value: true
12 |
13 | steps:
14 | - script: echo CodeQL plugin will be auto injected on default branch
15 | displayName: 'Running pipeline for static analysis'
16 |
17 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: 'run-tests'
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 | schedule:
9 | - cron: '0 2 * * SUN'
10 |
11 | env:
12 | sample: '${{ github.workspace }}/test/sample'
13 | build: '${{ github.workspace }}/test/sample/build'
14 | result: '${{ github.workspace }}/test/sample/build/results.sarif'
15 | config: Release
16 |
17 | jobs:
18 | build_and_test:
19 | name: Build and Test
20 | runs-on: windows-latest
21 |
22 | steps:
23 | - name: Checkout action
24 | uses: actions/checkout@v2.3.4
25 |
26 | - name: Setup Node.js
27 | uses: actions/setup-node@v2.4.0
28 | with:
29 | node-version: '12.x'
30 | - name: Build
31 | run: npm install
32 | - name: Test
33 | run: npm test
34 |
35 | functional_test:
36 | name: Functional - ${{ matrix.test }}
37 | runs-on: windows-latest
38 | strategy:
39 | fail-fast: false
40 | matrix:
41 | include:
42 | - test: Regular
43 | outcome: success
44 | ruleset: NativeRecommendedRules.ruleset
45 | ignoredPaths: test/sample/failure
46 | - test: Custom ruleset
47 | outcome: success
48 | ruleset: test/sample/Custom.ruleset
49 | ignoredPaths: test/sample/failure
50 | - test: Invalid ruleset
51 | outcome: failure
52 | ruleset: test/sample/Invalid.ruleset
53 | ignoredPaths: test/sample/failure
54 | - test: Compilation Error
55 | outcome: failure
56 | ruleset: NativeRecommendedRules.ruleset
57 | ignoredPaths: ''
58 |
59 | steps:
60 | - name: Checkout action
61 | uses: actions/checkout@v2.3.4
62 |
63 | - name: Configure CMake
64 | run: cmake ${{ env.sample }}/CMakeLists.txt -B ${{ env.build }} -DCMAKE_BUILD_TYPE=${{ env.config }}
65 |
66 | - name: Run action
67 | id: run_action
68 | continue-on-error: true
69 | uses: ./
70 | with:
71 | cmakeBuildDirectory: ${{ env.build }}
72 | buildConfiguration: ${{ env.config }}
73 | resultsPath: ${{ env.result }}
74 | ruleset: ${{ matrix.ruleset }}
75 | ignoreSystemHeaders: true
76 | ignoredPaths: ${{ matrix.ignoredPaths }}
77 |
78 | - name: Validate expected action outcome
79 | if: steps.run_action.outcome != matrix.outcome
80 | run: exit 1
81 |
82 | - name: Upload SARIF artifact
83 | if: steps.run_action.outcome == 'success'
84 | uses: actions/upload-artifact@v2
85 | with:
86 | name: sarif-file
87 | path: ${{ steps.run_action.outputs.sarif }}
88 | if-no-files-found: error
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | node_modules/
3 | test/sample/build/
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Microsoft C++ Code Analysis Action
2 |
3 | This actions run code analysis for any CMake project built with the Microsoft Visual C++ Compiler. The analysis
4 | will produce SARIF results that can be uploaded to the GitHub Code Scanning Alerts experience and/or included as
5 | an artifact to view locally in the Sarif Viewer VSCode Extension.
6 |
7 | ## Usage
8 |
9 | ### Pre-requisites
10 |
11 | Include a workflow `.yml` file using an [example](#example) below as a template. Run the `msvc-code-analysis-action`
12 | after configuring CMake for your project. Building the project is only required if the C++ source files involve the use
13 | of generated files.
14 |
15 |
16 | ### Input Parameters
17 |
18 | Description of all input parameters: [action.yml](https://github.com/microsoft/msvc-code-analysis-action/blob/main/action.yml)
19 |
20 | ### Example
21 |
22 | ```yml
23 | env:
24 | # Path to the CMake build directory.
25 | build: '${{ github.workspace }}/build'
26 | config: 'Debug'
27 |
28 | jobs:
29 | analyze:
30 | name: Analyze
31 | runs-on: windows-latest
32 |
33 | steps:
34 | - name: Checkout repository
35 | uses: actions/checkout@v2
36 |
37 | - name: Configure CMake
38 | run: cmake -B ${{ env.build }} -DCMAKE_BUILD_TYPE=${{ env.config }}
39 |
40 | # Build is not required unless generated source files are used
41 | # - name: Build CMake
42 | # run: cmake --build ${{ env.build }} --config ${{ env.config }}
43 |
44 | - name: Run MSVC Code Analysis
45 | uses: microsoft/msvc-code-analysis-action@v0.1.1
46 | # Provide a unique ID to access the sarif output path
47 | id: run-analysis
48 | with:
49 | cmakeBuildDirectory: ${{ env.build }}
50 | buildConfiguration: ${{ env.config }}
51 | # Ruleset file that will determine what checks will be run
52 | ruleset: NativeRecommendedRules.ruleset
53 | # Paths to ignore analysis of CMake targets and includes
54 | # ignoredPaths: ${{ github.workspace }}/dependencies;${{ github.workspace }}/test
55 |
56 | # Upload SARIF file to GitHub Code Scanning Alerts
57 | - name: Upload SARIF to GitHub
58 | uses: github/codeql-action/upload-sarif@v2
59 | with:
60 | sarif_file: ${{ steps.run-analysis.outputs.sarif }}
61 |
62 | # Upload SARIF file as an Artifact to download and view
63 | - name: Upload SARIF as an Artifact
64 | uses: actions/upload-artifact@v2
65 | with:
66 | name: sarif-file
67 | path: ${{ steps.run-analysis.outputs.sarif }}
68 | ```
69 |
70 | ### Warning Configuration
71 |
72 | By the default the action will use the set of warnings on by default inside of Visual Studio. However the tool can be
73 | configured to use any Ruleset either shipped with Visual Studio or user defined. For the best results it is
74 | recommended to use a custom Ruleset that adds/removes warnings on-top an existing Ruleset. This ensures that the user
75 | does not miss out on any new warnings are created. Refer to the
76 | [documentation on Rulesets](https://docs.microsoft.com/cpp/code-quality/using-rule-sets-to-specify-the-cpp-rules-to-run)
77 | for more information.
78 |
79 | #### Example Ruleset
80 |
81 | ```xml
82 |
83 |
84 |
85 |
86 |
88 |
89 |
90 |
91 |
92 | ```
93 |
94 | #### Suppression
95 |
96 | Ruleset are the main form of configuration but for a lightweight approach to suppress warnings you can pass options
97 | directly to the compiler.
98 |
99 | ```yml
100 | id: run-analysis
101 | with:
102 | additionalArgs: /wd6001 /wd6011 # Suppress C6001 & C6011
103 | # ....
104 | ```
105 |
106 | ## Contributing
107 |
108 | This project welcomes contributions and suggestions. Most contributions require you to agree to a
109 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
110 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
111 |
112 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide
113 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
114 | provided by the bot. You will only need to do this once across all repos using our CLA.
115 |
116 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
117 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
118 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
119 |
120 | ## Trademarks
121 |
122 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
123 | trademarks or logos is subject to and must follow
124 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
125 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
126 | Any use of third-party trademarks or logos are subject to those third-party's policies.
127 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
40 |
41 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # TODO: The maintainer of this repo has not yet edited this file
2 |
3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project?
4 |
5 | - **No CSS support:** Fill out this template with information about how to file issues and get help.
6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport).
7 | - **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide.
8 |
9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.*
10 |
11 | # Support
12 |
13 | ## How to file issues and get help
14 |
15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing
16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or
17 | feature request as a new Issue.
18 |
19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE
20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER
21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**.
22 |
23 | ## Microsoft Support Policy
24 |
25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
26 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Microsoft C++ Code Analysis Action'
2 | description: 'Run Microsoft C++ Code Analysis to produce SARIF files for use in github/codeql-action/upload-sarif@v1'
3 | inputs:
4 | cmakeBuildDirectory:
5 | description: 'The CMake build directory that should already be generated.'
6 | required: true
7 | buildConfiguration:
8 | description: 'The build Configuration (Release, Debug, etc.) to use. This is required if using a
9 | multi-configuration CMake generator.'
10 | required: false
11 | ruleset:
12 | description: 'Ruleset file used to determine what checks are run.'
13 | default: 'NativeRecommendedRules.ruleset'
14 | ignoredPaths:
15 | description: 'Identical to setting "ignoredTargetPaths" and "ignoreSystemHeaders" for the given path. This
16 | is recommended over either option seperately.'
17 | ignoredTargetPaths:
18 | description: 'Any CMake targets defined inside these paths will be excluded from analysis. This is useful
19 | for excluding tests or locally built dependencies. List is ";" seperated, requires complete
20 | directory paths and can be absolute or relative to "github.workspace"'
21 | required: false
22 | ignoredIncludePaths:
23 | description: 'Any includes contained inside these path will be excluded from analysis. This will only filter
24 | existing paths add not add any additional includes to the compiler. This is useful for excluding
25 | target includes or other custom includes added to CMake. List is ";" seperated, requires complete
26 | directory paths and can be absolute or relative to "github.workspace"'
27 | required: false
28 | ignoreSystemHeaders:
29 | description: 'Uses /external arguments to ignore warnings from any headers marked as SYSTEM in CMake.'
30 | default: true
31 | resultsPath:
32 | description: 'Optional path to generate the SARIF file to. If not supplied "results.sarif" will be created in
33 | the CMake build directory. Path can be absolute or relative to "github.workspace".'
34 | required: false
35 | loadImplicitCompilerEnv:
36 | description: 'Load implicit includes/libs for the given MSVC toolset using Visual Studio Command Prompt. Set to
37 | false if already loaded or a custom include path is needed.'
38 | default: true
39 | additionalArgs:
40 | description: 'Optional parameters to pass to every instance of the compiler.'
41 | required: false
42 | outputs:
43 | sarif:
44 | description: 'The path to the SARIF file that is generated containing all the results.'
45 |
46 | runs:
47 | using: 'node16'
48 | main: 'dist/index.js'
49 |
--------------------------------------------------------------------------------
/dist/licenses.txt:
--------------------------------------------------------------------------------
1 | @actions/core
2 | MIT
3 | The MIT License (MIT)
4 |
5 | Copyright 2019 GitHub
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 |
11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12 |
13 | @actions/exec
14 | MIT
15 | The MIT License (MIT)
16 |
17 | Copyright 2019 GitHub
18 |
19 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 |
25 | @actions/http-client
26 | MIT
27 | Actions Http Client for Node.js
28 |
29 | Copyright (c) GitHub, Inc.
30 |
31 | All rights reserved.
32 |
33 | MIT License
34 |
35 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
36 | associated documentation files (the "Software"), to deal in the Software without restriction,
37 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
38 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
39 | subject to the following conditions:
40 |
41 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
42 |
43 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
44 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
45 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
46 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
47 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
48 |
49 |
50 | @actions/io
51 | MIT
52 | The MIT License (MIT)
53 |
54 | Copyright 2019 GitHub
55 |
56 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
57 |
58 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
59 |
60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
61 |
62 | balanced-match
63 | MIT
64 | (MIT)
65 |
66 | Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
67 |
68 | Permission is hereby granted, free of charge, to any person obtaining a copy of
69 | this software and associated documentation files (the "Software"), to deal in
70 | the Software without restriction, including without limitation the rights to
71 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
72 | of the Software, and to permit persons to whom the Software is furnished to do
73 | so, subject to the following conditions:
74 |
75 | The above copyright notice and this permission notice shall be included in all
76 | copies or substantial portions of the Software.
77 |
78 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
79 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
80 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
81 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
82 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
83 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
84 | SOFTWARE.
85 |
86 |
87 | brace-expansion
88 | MIT
89 | MIT License
90 |
91 | Copyright (c) 2013 Julian Gruber
92 |
93 | Permission is hereby granted, free of charge, to any person obtaining a copy
94 | of this software and associated documentation files (the "Software"), to deal
95 | in the Software without restriction, including without limitation the rights
96 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
97 | copies of the Software, and to permit persons to whom the Software is
98 | furnished to do so, subject to the following conditions:
99 |
100 | The above copyright notice and this permission notice shall be included in all
101 | copies or substantial portions of the Software.
102 |
103 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
104 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
105 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
106 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
107 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
108 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
109 | SOFTWARE.
110 |
111 |
112 | concat-map
113 | MIT
114 | This software is released under the MIT license:
115 |
116 | Permission is hereby granted, free of charge, to any person obtaining a copy of
117 | this software and associated documentation files (the "Software"), to deal in
118 | the Software without restriction, including without limitation the rights to
119 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
120 | the Software, and to permit persons to whom the Software is furnished to do so,
121 | subject to the following conditions:
122 |
123 | The above copyright notice and this permission notice shall be included in all
124 | copies or substantial portions of the Software.
125 |
126 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
127 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
128 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
129 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
130 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
131 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
132 |
133 |
134 | fs.realpath
135 | ISC
136 | The ISC License
137 |
138 | Copyright (c) Isaac Z. Schlueter and Contributors
139 |
140 | Permission to use, copy, modify, and/or distribute this software for any
141 | purpose with or without fee is hereby granted, provided that the above
142 | copyright notice and this permission notice appear in all copies.
143 |
144 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
145 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
146 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
147 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
148 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
149 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
150 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
151 |
152 | ----
153 |
154 | This library bundles a version of the `fs.realpath` and `fs.realpathSync`
155 | methods from Node.js v0.10 under the terms of the Node.js MIT license.
156 |
157 | Node's license follows, also included at the header of `old.js` which contains
158 | the licensed code:
159 |
160 | Copyright Joyent, Inc. and other Node contributors.
161 |
162 | Permission is hereby granted, free of charge, to any person obtaining a
163 | copy of this software and associated documentation files (the "Software"),
164 | to deal in the Software without restriction, including without limitation
165 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
166 | and/or sell copies of the Software, and to permit persons to whom the
167 | Software is furnished to do so, subject to the following conditions:
168 |
169 | The above copyright notice and this permission notice shall be included in
170 | all copies or substantial portions of the Software.
171 |
172 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
173 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
174 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
175 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
176 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
177 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
178 | DEALINGS IN THE SOFTWARE.
179 |
180 |
181 | glob
182 | ISC
183 | The ISC License
184 |
185 | Copyright (c) Isaac Z. Schlueter and Contributors
186 |
187 | Permission to use, copy, modify, and/or distribute this software for any
188 | purpose with or without fee is hereby granted, provided that the above
189 | copyright notice and this permission notice appear in all copies.
190 |
191 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
192 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
193 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
194 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
195 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
196 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
197 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
198 |
199 | ## Glob Logo
200 |
201 | Glob's logo created by Tanya Brassie , licensed
202 | under a Creative Commons Attribution-ShareAlike 4.0 International License
203 | https://creativecommons.org/licenses/by-sa/4.0/
204 |
205 |
206 | inflight
207 | ISC
208 | The ISC License
209 |
210 | Copyright (c) Isaac Z. Schlueter
211 |
212 | Permission to use, copy, modify, and/or distribute this software for any
213 | purpose with or without fee is hereby granted, provided that the above
214 | copyright notice and this permission notice appear in all copies.
215 |
216 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
217 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
218 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
219 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
220 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
221 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
222 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
223 |
224 |
225 | inherits
226 | ISC
227 | The ISC License
228 |
229 | Copyright (c) Isaac Z. Schlueter
230 |
231 | Permission to use, copy, modify, and/or distribute this software for any
232 | purpose with or without fee is hereby granted, provided that the above
233 | copyright notice and this permission notice appear in all copies.
234 |
235 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
236 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
237 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
238 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
239 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
240 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
241 | PERFORMANCE OF THIS SOFTWARE.
242 |
243 |
244 |
245 | minimatch
246 | ISC
247 | The ISC License
248 |
249 | Copyright (c) Isaac Z. Schlueter and Contributors
250 |
251 | Permission to use, copy, modify, and/or distribute this software for any
252 | purpose with or without fee is hereby granted, provided that the above
253 | copyright notice and this permission notice appear in all copies.
254 |
255 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
256 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
257 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
258 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
259 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
260 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
261 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
262 |
263 |
264 | once
265 | ISC
266 | The ISC License
267 |
268 | Copyright (c) Isaac Z. Schlueter and Contributors
269 |
270 | Permission to use, copy, modify, and/or distribute this software for any
271 | purpose with or without fee is hereby granted, provided that the above
272 | copyright notice and this permission notice appear in all copies.
273 |
274 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
275 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
276 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
277 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
278 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
279 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
280 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
281 |
282 |
283 | path-is-absolute
284 | MIT
285 | The MIT License (MIT)
286 |
287 | Copyright (c) Sindre Sorhus (sindresorhus.com)
288 |
289 | Permission is hereby granted, free of charge, to any person obtaining a copy
290 | of this software and associated documentation files (the "Software"), to deal
291 | in the Software without restriction, including without limitation the rights
292 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
293 | copies of the Software, and to permit persons to whom the Software is
294 | furnished to do so, subject to the following conditions:
295 |
296 | The above copyright notice and this permission notice shall be included in
297 | all copies or substantial portions of the Software.
298 |
299 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
300 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
301 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
302 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
303 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
304 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
305 | THE SOFTWARE.
306 |
307 |
308 | rimraf
309 | ISC
310 | The ISC License
311 |
312 | Copyright (c) Isaac Z. Schlueter and Contributors
313 |
314 | Permission to use, copy, modify, and/or distribute this software for any
315 | purpose with or without fee is hereby granted, provided that the above
316 | copyright notice and this permission notice appear in all copies.
317 |
318 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
319 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
320 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
321 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
322 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
323 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
324 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
325 |
326 |
327 | tmp
328 | MIT
329 | The MIT License (MIT)
330 |
331 | Copyright (c) 2014 KARASZI István
332 |
333 | Permission is hereby granted, free of charge, to any person obtaining a copy
334 | of this software and associated documentation files (the "Software"), to deal
335 | in the Software without restriction, including without limitation the rights
336 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
337 | copies of the Software, and to permit persons to whom the Software is
338 | furnished to do so, subject to the following conditions:
339 |
340 | The above copyright notice and this permission notice shall be included in all
341 | copies or substantial portions of the Software.
342 |
343 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
344 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
345 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
346 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
347 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
348 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
349 | SOFTWARE.
350 |
351 |
352 | tunnel
353 | MIT
354 | The MIT License (MIT)
355 |
356 | Copyright (c) 2012 Koichi Kobayashi
357 |
358 | Permission is hereby granted, free of charge, to any person obtaining a copy
359 | of this software and associated documentation files (the "Software"), to deal
360 | in the Software without restriction, including without limitation the rights
361 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
362 | copies of the Software, and to permit persons to whom the Software is
363 | furnished to do so, subject to the following conditions:
364 |
365 | The above copyright notice and this permission notice shall be included in
366 | all copies or substantial portions of the Software.
367 |
368 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
369 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
370 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
371 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
372 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
373 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
374 | THE SOFTWARE.
375 |
376 |
377 | uuid
378 | MIT
379 | The MIT License (MIT)
380 |
381 | Copyright (c) 2010-2020 Robert Kieffer and other contributors
382 |
383 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
384 |
385 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
386 |
387 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
388 |
389 |
390 | wrappy
391 | ISC
392 | The ISC License
393 |
394 | Copyright (c) Isaac Z. Schlueter and Contributors
395 |
396 | Permission to use, copy, modify, and/or distribute this software for any
397 | purpose with or without fee is hereby granted, provided that the above
398 | copyright notice and this permission notice appear in all copies.
399 |
400 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
401 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
402 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
403 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
404 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
405 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
406 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
407 |
--------------------------------------------------------------------------------
/dist/vc_env.bat:
--------------------------------------------------------------------------------
1 | @CALL %1 %2 -vcvars_ver=%3
2 | @ECHO INCLUDE=%INCLUDE%
3 | @ECHO LIB=%LIB%
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const core = require('@actions/core');
4 | const exec = require('@actions/exec');
5 | const fs = require('fs');
6 | const io = require('@actions/io');
7 | const path = require('path');
8 | const tmp = require('tmp');
9 | const toolrunner = require('@actions/exec/lib/toolrunner');
10 |
11 | const CMakeApiClientName = "client-msvc-ca-action";
12 | // Paths relative to absolute path to cl.exe
13 | const RelativeRulesetPath = '..\\..\\..\\..\\..\\..\\..\\..\\Team Tools\\Static Analysis Tools\\Rule Sets';
14 | const RelativeToolsetPath = '..\\..\\..\\..';
15 | const RelativeCommandPromptPath = '..\\..\\..\\..\\..\\..\\..\\Auxiliary\\Build\\vcvarsall.bat';
16 |
17 | /**
18 | * Validate if the given directory both exists and is non-empty.
19 | * @param {string} targetDir directory to test
20 | * @returns {boolean} true if the directory is empty
21 | */
22 | function isDirectoryEmpty(targetDir) {
23 | return !targetDir || !fs.existsSync(targetDir) || (fs.readdirSync(targetDir).length) == 0;
24 | }
25 |
26 | /**
27 | * Validate if the targetDir is either equal or a sub-directory of any path in parentDirs
28 | * @param {string[]} parentDirs parent directories
29 | * @param {string} targetDir directory to test
30 | * @returns {boolean} true if a sub-directory is found
31 | */
32 | function containsSubdirectory(parentDirs, targetDir) {
33 | const normalizedTarget = path.normalize(targetDir);
34 | return parentDirs.some((parentDir) => normalizedTarget.startsWith(path.normalize(parentDir)));
35 | }
36 |
37 | /**
38 | * Get normalized relative path from a given file/directory.
39 | * @param {string} fromPath path to join relative path to
40 | * @param {string} relativePath relative path to append
41 | * @returns normalized path
42 | */
43 | function getRelativeTo(fromPath, relativePath) {
44 | return path.normalize(path.join(fromPath, relativePath))
45 | }
46 |
47 | /**
48 | * Validate and resolve path by making non-absolute paths relative to GitHub
49 | * repository root.
50 | * @param {string} unresolvedPath path to resolve
51 | * @returns the resolved absolute path
52 | */
53 | function resolvePath(unresolvedPath) {
54 | return path.normalize(path.isAbsolute(unresolvedPath) ?
55 | unresolvedPath : path.join(process.env.GITHUB_WORKSPACE, unresolvedPath));
56 | }
57 |
58 | /**
59 | * Validate and resolve action input path by making non-absolute paths relative to
60 | * GitHub repository root.
61 | * @param {string} input name of GitHub action input variable
62 | * @param {boolean} required if true the input must be non-empty
63 | * @returns the absolute path to the input path if specified
64 | */
65 | function resolveInputPath(input, required = false) {
66 | let inputPath = core.getInput(input);
67 | if (!inputPath) {
68 | if (required) {
69 | throw new Error(input + " input path can not be empty.");
70 | }
71 |
72 | return undefined;
73 | }
74 |
75 | return resolvePath(inputPath, required);
76 | }
77 |
78 | /**
79 | * Validate and resolve action input paths making non-absolute paths relative to
80 | * GitHub repository root. Paths are seperated by the provided string.
81 | * @param {string} input name of GitHub action input variable
82 | * @param {boolean} required if true the input must be non-empty
83 | * @returns the absolute path to the input path if specified
84 | */
85 | function resolveInputPaths(input, required = false, seperator = ';') {
86 | const inputPaths = core.getInput(input);
87 | if (!inputPaths) {
88 | if (required) {
89 | throw new Error(input + " input paths can not be empty.");
90 | }
91 |
92 | return [];
93 | }
94 |
95 | return inputPaths.split(seperator)
96 | .map((inputPath) => resolvePath(inputPath))
97 | .filter((inputPath) => inputPath);
98 | }
99 |
100 | /**
101 | * Create a query file for the CMake API
102 | * @param {string} apiDir CMake API directory '.cmake/api/v1'
103 | */
104 | async function createApiQuery(apiDir) {
105 | const queryDir = path.join(apiDir, "query", CMakeApiClientName);
106 | if (!fs.existsSync(queryDir)) {
107 | await io.mkdirP(queryDir);
108 | }
109 |
110 | const queryFile = path.join(queryDir, "query.json");
111 | const queryData = {
112 | "requests": [
113 | { kind: "codemodel", version: 2 },
114 | { kind: "toolchains", version: 1 }
115 | ]};
116 |
117 | try {
118 | fs.writeFileSync(queryFile, JSON.stringify(queryData), 'utf-8');
119 | } catch (err) {
120 | throw new Error("Failed to write query.json file for CMake API.", err);
121 | }
122 | }
123 |
124 | /**
125 | * Read and parse the given JSON reply file.
126 | * @param {string} replyFile absolute path to JSON reply
127 | * @returns parsed JSON data of the reply file
128 | */
129 | function parseReplyFile(replyFile) {
130 | if (!fs.existsSync(replyFile)) {
131 | throw new Error("Failed to find CMake API reply file: " + replyFile);
132 | }
133 |
134 | let jsonData = fs.readFileSync(replyFile, (err) => {
135 | if (err) {
136 | throw new Error("Failed to read CMake API reply file: " + replyFile, err);
137 | }
138 | });
139 |
140 | return JSON.parse(jsonData);
141 | }
142 |
143 | /**
144 | * Get the JSON filepath for the given response kind.
145 | * @param {string} replyDir CMake API directory for replies '.cmake/api/v1/reply'
146 | * @param {object} indexReply parsed JSON data from index-xxx.json reply
147 | * @param {string} kind the kind of response to search for
148 | * @returns the absolute path to the JSON response file, null if not found
149 | */
150 | function getResponseFilepath(replyDir, clientResponses, kind) {
151 | const response = clientResponses.find((response) => response["kind"] == kind);
152 | return response ? path.join(replyDir, response.jsonFile) : null;
153 | }
154 |
155 | /**
156 | * Information extracted from CMake API index reply which details all other requested responses.
157 | * @param {string} replyDir CMake API directory for replies '.cmake/api/v1/reply'
158 | * @param {object} indexReply parsed JSON data from index-xxx.json reply
159 | */
160 | function ReplyIndexInfo(replyDir, indexReply) {
161 | const clientResponses = indexReply.reply[CMakeApiClientName]["query.json"].responses;
162 | this.codemodelResponseFile = getResponseFilepath(replyDir, clientResponses, "codemodel");
163 | this.toolchainsResponseFile = getResponseFilepath(replyDir, clientResponses, "toolchains");
164 | this.version = indexReply.cmake.version.string;
165 | }
166 |
167 | /**
168 | * Load the information needed from the reply index file for the CMake API
169 | * @param {string} apiDir CMake API directory '.cmake/api/v1'
170 | * @returns ReplyIndexInfo info extracted from index-xxx.json reply
171 | */
172 | function getApiReplyIndex(apiDir) {
173 | const replyDir = path.join(apiDir, "reply");
174 |
175 | let indexFilepath;
176 | if (fs.existsSync(replyDir)) {
177 | for (const filename of fs.readdirSync(replyDir)) {
178 | if (filename.startsWith("index-")) {
179 | // get the most recent index query file (ordered lexicographically)
180 | const filepath = path.join(replyDir, filename);
181 | if (!indexFilepath || filepath > indexFilepath) {
182 | indexFilepath = filepath;
183 | }
184 | };
185 | }
186 | }
187 |
188 | if (!indexFilepath) {
189 | throw new Error("Failed to find CMake API index reply file.");
190 | }
191 |
192 | const indexReply = parseReplyFile(indexFilepath);
193 | const replyIndexInfo = new ReplyIndexInfo(replyDir, indexReply);
194 |
195 | core.info(`Loaded '${indexFilepath}' reply generated from CMake API.`);
196 |
197 | return replyIndexInfo;
198 | }
199 |
200 | /**
201 | * Load reply data from the CMake API. This will:
202 | * - Create a query file in cmake API directory requesting data needed
203 | * - Re-run CMake on build directory to generate reply data
204 | * - Extract required information from the index-xxx.json reply
205 | * - Validate the version of CMake to ensure required reply data exists
206 | * @param {string} buildRoot build directory of CMake project
207 | * @return ReplyIndexInfo info extracted from index-xxx.json reply
208 | */
209 | async function loadCMakeApiReplies(buildRoot) {
210 | if (isDirectoryEmpty(buildRoot)) {
211 | throw new Error("CMake build root must exist, be non-empty and be configured with CMake");
212 | }
213 |
214 | // validate CMake can be found on the PATH
215 | await io.which("cmake", true);
216 |
217 | // create CMake API query file for the generation of replies needed
218 | const apiDir = path.join(buildRoot, ".cmake/api/v1");
219 | await createApiQuery(apiDir);
220 |
221 | // regenerate CMake build directory to acquire CMake file API reply
222 | core.info(`Running CMake to generate reply data.`);
223 | try {
224 | await exec.exec("cmake", [ buildRoot ])
225 | } catch (err) {
226 | throw new Error(`CMake failed to reconfigure project with error: ${err}`);
227 | }
228 |
229 | // load reply index generated from the CMake Api
230 | const replyIndexInfo = getApiReplyIndex(apiDir);
231 | if (replyIndexInfo.version < "3.20.5") {
232 | throw new Error("Action requires CMake version >= 3.20.5");
233 | }
234 |
235 | return replyIndexInfo;
236 | }
237 |
238 | /**
239 | * Information on compiler include path.
240 | * @param {string} path the absolute path to the include directory
241 | * @param {boolean} isSystem true if this should be treated as a CMake SYSTEM path
242 | */
243 | function IncludePath(path, isSystem) {
244 | this.path = path;
245 | this.isSystem = isSystem;
246 | }
247 |
248 | /**
249 | * Information about the language and compiler being used to compile a source file.
250 | * @param {object} toolchain ReplyIndexInfo info extracted from index-xxx.json reply
251 | */
252 | function ToolchainInfo(toolchain) {
253 | this.language = toolchain.language;
254 | this.path = toolchain.compiler.path;
255 | this.version = toolchain.compiler.version;
256 | this.includes = (toolchain.compiler.implicit.includeDirectories || []).map(
257 | (include) => new IncludePath(include, true));
258 |
259 | // extract toolset-version & host/target arch from folder layout in VS
260 | this.toolsetVersion = path.basename(getRelativeTo(this.path, RelativeToolsetPath));
261 | const targetDir = path.dirname(this.path);
262 | const hostDir = path.dirname(targetDir);
263 | this.targetArch = path.basename(targetDir);
264 | switch (path.basename(hostDir).toLowerCase()) {
265 | case 'hostx86':
266 | this.hostArch = 'x86';
267 | break;
268 | case 'hostx64':
269 | this.hostArch = 'x64';
270 | break;
271 | default:
272 | throw new Error('Unknown MSVC toolset layout');
273 | }
274 | }
275 |
276 | /**
277 | * Parse the toolchain-xxx.json file to find information on any MSVC toolchains used. If none are
278 | * found issue an error.
279 | * @param {ReplyIndexInfo} replyIndexInfo ReplyIndexInfo info extracted from index-xxx.json reply
280 | * @returns Toolchain info extracted from toolchain-xxx.json
281 | */
282 | function loadToolchainMap(replyIndexInfo) {
283 | if (!fs.existsSync(replyIndexInfo.toolchainsResponseFile)) {
284 | throw new Error("Failed to load toolchains response from CMake API");
285 | }
286 |
287 | const toolchainMap = {};
288 | const toolchains = parseReplyFile(replyIndexInfo.toolchainsResponseFile);
289 | const cToolchain = toolchains.toolchains.find(
290 | (t) => t.language == "C" && t.compiler.id == "MSVC");
291 | if (cToolchain) {
292 | toolchainMap[cToolchain.language] = new ToolchainInfo(cToolchain);
293 | }
294 |
295 | const cxxToolchain = toolchains.toolchains.find(
296 | (t) => t.language == "CXX" && t.compiler.id == "MSVC");
297 | if (cxxToolchain) {
298 | toolchainMap[cxxToolchain.language] = new ToolchainInfo(cxxToolchain);
299 | }
300 |
301 |
302 | if (Object.keys(toolchainMap).length === 0) {
303 | throw new Error("Action requires use of MSVC for either/both C or C++.");
304 | }
305 |
306 | return toolchainMap;
307 | }
308 |
309 | /**
310 | * Information on each compilation unit extracted from the CMake targets.
311 | * @param {object} group compilation data shared between one or more source files
312 | * @param {string} source absolute path to source file being compiled
313 | */
314 | function CompileCommand(group, source) {
315 | // Filepath to source file being compiled
316 | this.source = source;
317 | // Compiler language used
318 | this.language = group.language;
319 | // C++ Standard
320 | this.standard = group.languageStandard ? group.languageStandard.standard : undefined;
321 | // Compile command line fragments appended into a single string
322 | this.args = (group.compileCommandFragments || []).map((c) => c.fragment).join(" ");
323 | // includes, both regular and system
324 | this.includes = (group.includes || []).map((inc) =>
325 | new IncludePath(inc.path, inc.isSystem || false));
326 | // defines
327 | this.defines = (group.defines || []).map((d) => d.define);
328 | }
329 |
330 | /**
331 | * Parse the codemodel-xxx.json and each target-xxx.json to find information on required to compile
332 | * each source file in the project.
333 | * @param {ReplyIndexInfo} replyIndexInfo ReplyIndexInfo info extracted from index-xxx.json reply
334 | * @returns CompileCommand information for each compiled source file in the project
335 | */
336 | function loadCompileCommands(replyIndexInfo, buildConfiguration, excludedTargetPaths) {
337 | if (!fs.existsSync(replyIndexInfo.codemodelResponseFile)) {
338 | throw new Error("Failed to load codemodel response from CMake API");
339 | }
340 |
341 | let compileCommands = [];
342 | const codemodel = parseReplyFile(replyIndexInfo.codemodelResponseFile);
343 | const sourceRoot = codemodel.paths.source;
344 | const replyDir = path.dirname(replyIndexInfo.codemodelResponseFile);
345 | let configurations = codemodel.configurations;
346 | if (configurations.length > 1) {
347 | if (!buildConfiguration) {
348 | throw new Error("buildConfiguration is required for multi-config CMake Generators.");
349 | }
350 |
351 | configurations = configurations.filter((config) => buildConfiguration == config.name);
352 | if (configurations.length == 0) {
353 | throw new Error("buildConfiguration does not match any available in CMake project.");
354 | }
355 | } else if (buildConfiguration && configurations[0].name != buildConfiguration) {
356 | throw new Error(`buildConfiguration does not match '${configurations[0].name}' configuration used by CMake.`);
357 | }
358 |
359 | const codemodelInfo = configurations[0];
360 | for (const targetInfo of codemodelInfo.targets) {
361 | const targetDir = path.join(sourceRoot, codemodelInfo.directories[targetInfo.directoryIndex].source);
362 | if (containsSubdirectory(excludedTargetPaths, targetDir)) {
363 | continue;
364 | }
365 |
366 | const target = parseReplyFile(path.join(replyDir, targetInfo.jsonFile));
367 | for (const group of target.compileGroups || []) {
368 | for (const sourceIndex of group.sourceIndexes) {
369 | const source = path.join(sourceRoot, target.sources[sourceIndex].path);
370 | compileCommands.push(new CompileCommand(group, source));
371 | }
372 | }
373 | }
374 |
375 | return compileCommands;
376 | }
377 |
378 | /**
379 | * Find path to EspXEngine.dll as it only exists in host/target bin for MSVC Visual Studio release.
380 | * @param {ToolchainInfo} toolchain information on the toolchain being used
381 | * @returns absolute path to EspXEngine.dll
382 | */
383 | function findEspXEngine(toolchain) {
384 | const hostDir = path.dirname(path.dirname(toolchain.path));
385 | const espXEnginePath = path.join(hostDir, toolchain.hostArch, 'EspXEngine.dll');
386 | if (fs.existsSync(espXEnginePath)) {
387 | return espXEnginePath;
388 | }
389 |
390 | throw new Error(`Unable to find: ${espXEnginePath}`);
391 | }
392 |
393 | /**
394 | * Find official ruleset directory using the known path of MSVC compiler in Visual Studio.
395 | * @param {ToolchainInfo} toolchain information on the toolchain being used
396 | * @returns absolute path to directory containing all Visual Studio rulesets
397 | */
398 | function findRulesetDirectory(toolchain) {
399 | const rulesetDirectory = getRelativeTo(toolchain.path, RelativeRulesetPath);
400 | return fs.existsSync(rulesetDirectory) ? rulesetDirectory : undefined;
401 | }
402 |
403 | /**
404 | * Find ruleset first searching relative to GitHub repository and then relative to the official ruleset directory
405 | * shipped in Visual Studio.
406 | * @param {string} rulesetDirectory path to directory containing all Visual Studio rulesets
407 | * @returns path to ruleset found locally or inside Visual Studio
408 | */
409 | function findRuleset(rulesetDirectory) {
410 | const repoRulesetPath = resolveInputPath("ruleset");
411 | if (!repoRulesetPath) {
412 | return undefined;
413 | } else if (fs.existsSync(repoRulesetPath)) {
414 | core.info(`Found local ruleset: ${repoRulesetPath}`);
415 | return repoRulesetPath;
416 | }
417 |
418 | // search official ruleset directory that ships inside of Visual Studio
419 | const rulesetPath = core.getInput("ruleset");
420 | if (rulesetDirectory != undefined) {
421 | const officialRulesetPath = path.join(rulesetDirectory, rulesetPath);
422 | if (fs.existsSync(officialRulesetPath)) {
423 | core.info(`Found official ruleset: ${officialRulesetPath}`);
424 | return officialRulesetPath;
425 | }
426 | } else {
427 | core.warning("Unable to find official rulesets shipped with Visual Studio.");
428 | }
429 |
430 | throw new Error(`Unable to find local or official ruleset specified: ${rulesetPath}`);
431 | }
432 |
433 | /**
434 | * Options to enable/disable different compiler features.
435 | */
436 | function CompilerCommandOptions() {
437 | // Build configuration to use when using a multi-config CMake generator.
438 | this.buildConfiguration = core.getInput("buildConfiguration");
439 | // Use /external command line options to ignore warnings in CMake SYSTEM headers.
440 | this.ignoreSystemHeaders = core.getInput("ignoreSystemHeaders");
441 | // Toggle whether implicit includes/libs are loaded from Visual Studio Command Prompt
442 | this.loadImplicitCompilerEnv = core.getInput("loadImplicitCompilerEnv");
443 | // Ignore analysis on any CMake targets or includes.
444 | this.ignoredPaths = resolveInputPaths("ignoredPaths");
445 | this.ignoredTargetPaths = this.ignoredPaths || [];
446 | this.ignoredTargetPaths = this.ignoredTargetPaths.concat(resolveInputPaths("ignoredTargetPaths"));
447 | this.ignoredIncludePaths = this.ignoredPaths || [];
448 | this.ignoredIncludePaths = this.ignoredIncludePaths.concat(resolveInputPaths("ignoredIncludePaths"));
449 | // Additional arguments to add the command-line of every analysis instance
450 | this.additionalArgs = core.getInput("additionalArgs");
451 | // TODO: add support to build precompiled headers before running analysis.
452 | this.usePrecompiledHeaders = false; // core.getInput("usePrecompiledHeaders");
453 | }
454 |
455 | /**
456 | * Construct all command-line arguments that will be common among all sources files of a given compiler.
457 | * @param {*} toolchain information on the toolchain being used
458 | * @param {CompilerCommandOptions} options options for different compiler features
459 | * @returns list of analyze arguments common to the given toolchain
460 | */
461 | function getCommonAnalyzeArguments(toolchain, options) {
462 | const args = ["/analyze:only", "/analyze:quiet", "/analyze:log:format:sarif", "/nologo"];
463 |
464 | const espXEngine = findEspXEngine(toolchain);
465 | args.push(`/analyze:plugin${espXEngine}`);
466 |
467 | const rulesetDirectory = findRulesetDirectory(toolchain);
468 | const rulesetPath = findRuleset(rulesetDirectory);
469 | if (rulesetPath != undefined) {
470 | args.push(`/analyze:ruleset${rulesetPath}`);
471 |
472 | // add ruleset directories incase user includes any official rulesets
473 | if (rulesetDirectory != undefined) {
474 | args.push(`/analyze:rulesetdirectory${rulesetDirectory}`);
475 | }
476 | } else {
477 | core.warning('Ruleset is not being used, all warnings will be enabled.');
478 | }
479 |
480 | if (options.ignoreSystemHeaders) {
481 | args.push(`/external:W0`);
482 | args.push(`/analyze:external-`);
483 | }
484 |
485 | if (options.additionalArgs) {
486 | args = args.concat(toolrunner.argStringToArray(options.additionalArgs));
487 | }
488 |
489 | return args;
490 | }
491 |
492 | /**
493 | * Extract the the implicit includes that should be used with the given compiler from the
494 | * Visual Studio command prompt corresponding with the toolchain used. This is required
495 | * as MSVC does not populate the CMake API `toolchain.implicit.includeDirectories` property.
496 | * @param {ToolchainInfo} toolchain information on the toolchain being used
497 | * @returns array of default includes used by the given MSVC toolset
498 | */
499 | async function extractEnvironmentFromCommandPrompt(toolchain) {
500 | // use bat file to output environment variable required after running 'vcvarsall.bat'
501 | const vcEnvScript = path.join(__dirname, "vc_env.bat");
502 | // init arguments for 'vcvarsall.bat' to match the toolset version/arch used
503 | const commandPromptPath = getRelativeTo(toolchain.path, RelativeCommandPromptPath);
504 | const arch = (toolchain.hostArch == toolchain.targetArch) ?
505 | toolchain.hostArch : `${toolchain.hostArch}_${toolchain.targetArch}`;
506 |
507 | core.info("Extracting environment from VS Command Prompt");
508 | const execOptions = { silent: true };
509 | const execOutput = await exec.getExecOutput(vcEnvScript,
510 | [commandPromptPath, arch, toolchain.toolsetVersion], execOptions);
511 | if (execOutput.exitCode != 0) {
512 | core.debug(execOutput.stdout);
513 | throw new Error("Failed to run VS Command Prompt to collect implicit includes/libs");
514 | }
515 |
516 | const env = { INCLUDE: "", LIB: "" };
517 | for (const line of execOutput.stdout.split(/\r?\n/)) {
518 | const index = line.indexOf('=');
519 | if (index != -1) {
520 | const envVar = line.substring(0, index);
521 | if (envVar in env) {
522 | env[envVar] = line.substring(index + 1);
523 | }
524 | }
525 | }
526 |
527 | return env;
528 | }
529 |
530 | /**
531 | * Construct all environment variables that will be common among all sources files of a given compiler.
532 | * @param {ToolchainInfo} toolchain information on the toolchain being used
533 | * @param {CompilerCommandOptions} options options for different compiler features
534 | * @returns map of environment variables common to the given toolchain
535 | */
536 | async function getCommonAnalyzeEnvironment(toolchain, options) {
537 | const env = {
538 | CAEmitSarifLog: "1", // enable compatibility mode as GitHub does not support some sarif options
539 | CAExcludePath: process.env.CAExcludePath || "",
540 | INCLUDE: process.env.INCLUDE || "",
541 | LIB: process.env.LIB || "",
542 | };
543 |
544 | if (options.loadImplicitCompilerEnv) {
545 | const commandPromptEnv = await extractEnvironmentFromCommandPrompt(toolchain);
546 | env.CAExcludePath += `;${commandPromptEnv.INCLUDE}`; // exclude all implicit includes
547 | env.INCLUDE += `;${commandPromptEnv.INCLUDE}`;
548 | env.LIB += `;${commandPromptEnv.LIB}`;
549 | }
550 |
551 | return env;
552 | }
553 |
554 | /**
555 | * Information required to run analysis on a single source file.
556 | * @param {string} source absolute path to the source file being compiled
557 | * @param {string} compiler absolute path to compiler used
558 | * @param {string[]} args all compilation and analyze arguments to pass to cl.exe
559 | * @param {[key: string]: string} env environment to use when running cl.exe
560 | * @param {string} sarifLog absolute path to SARIF log file that will be produced
561 | */
562 | function AnalyzeCommand(source, compiler, args, env, sarifLog) {
563 | this.source = source;
564 | this.compiler = compiler;
565 | this.args = args;
566 | this.env = env;
567 | this.sarifLog = sarifLog;
568 | }
569 |
570 | /**
571 | * Load information needed to compile and analyze each source file in the given CMake project.
572 | * This makes use of the CMake file API and other sources to collect this data.
573 | * @param {string} buildRoot absolute path to the build directory of the CMake project
574 | * @param {CompilerCommandOptions} options options for different compiler features
575 | * @returns list of information to compile and analyze each source file in the project
576 | */
577 | async function createAnalysisCommands(buildRoot, options) {
578 | const replyIndexInfo = await loadCMakeApiReplies(buildRoot);
579 | const toolchainMap = loadToolchainMap(replyIndexInfo);
580 | const compileCommands = loadCompileCommands(replyIndexInfo, options.buildConfiguration, options.ignoredTargetPaths);
581 |
582 | let commonArgsMap = {};
583 | let commonEnvMap = {};
584 | for (const toolchain of Object.values(toolchainMap)) {
585 | if (!(toolchain.path in commonArgsMap)) {
586 | commonArgsMap[toolchain.path] = getCommonAnalyzeArguments(toolchain, options);
587 | commonEnvMap[toolchain.path] = await getCommonAnalyzeEnvironment(toolchain, options);
588 | }
589 | }
590 |
591 | let analyzeCommands = []
592 | for (const command of compileCommands) {
593 | const toolchain = toolchainMap[command.language];
594 | if (toolchain) {
595 | let args = toolrunner.argStringToArray(command.args);
596 | const allIncludes = toolchain.includes.concat(command.includes);
597 | for (const include of allIncludes) {
598 | if ((options.ignoreSystemHeaders && include.isSystem) ||
599 | containsSubdirectory(options.ignoredIncludePaths, include.path)) {
600 | // TODO: filter compiler versions that don't support /external.
601 | args.push(`/external:I${include.path}`);
602 | } else {
603 | args.push(`/I${include.path}`);
604 | }
605 | }
606 |
607 | for (const define of command.defines) {
608 | args.push(`/D${define}`);
609 | }
610 |
611 | args.push(command.source);
612 |
613 | let sarifLog = null;
614 | try {
615 | sarifLog = tmp.fileSync({ postfix: '.sarif', discardDescriptor: true }).name;
616 | } catch (err) {
617 | // Clean up all temp SARIF logs
618 | analyzeCommands.forEach(command => fs.unlinkSync(command.sarifLog));
619 | throw Error(`Failed to create temporary file to write SARIF: ${err}`, err);
620 | }
621 |
622 | args.push(`/analyze:log${sarifLog}`);
623 |
624 | args = args.concat(commonArgsMap[toolchain.path]);
625 | analyzeCommands.push(new AnalyzeCommand(
626 | command.source, toolchain.path, args, commonEnvMap[toolchain.path], sarifLog));
627 | }
628 | }
629 |
630 | return analyzeCommands;
631 | }
632 |
633 | // TODO: use a more performant data-structure such a hash-set
634 | function ResultCache() {
635 | this.files = {};
636 | this.addIfUnique = function(sarifResult) {
637 | const id = sarifResult.ruleId;
638 | if (!id) {
639 | throw Error(`Found warning with no ID, resolve before continuing`);
640 | }
641 |
642 | const message = sarifResult.message ? sarifResult.message.text : undefined;
643 | if (!message) {
644 | throw Error(`Found warning with no message, resolve before continuing: ${id}`);
645 | }
646 |
647 | if (!sarifResult.locations || !sarifResult.locations[0] || !sarifResult.locations[0].physicalLocation) {
648 | throw Error(`Found warning with no location, resolve before continuing:\n${id}: ${message}`);
649 | }
650 |
651 | const physicalLocation = sarifResult.locations[0].physicalLocation;
652 | const file = physicalLocation.artifactLocation ? physicalLocation.artifactLocation.uri : undefined;
653 | const line = physicalLocation.region ? physicalLocation.region.startLine : undefined;
654 | const column = physicalLocation.region ? physicalLocation.region.startColumn : undefined;
655 | if (file == undefined || line == undefined || column == undefined) {
656 | throw Error(`Found warning with invalid location, resolve before continuing:\n${id}: ${message}`);
657 | }
658 |
659 | this.files[file] = this.files[file] || {};
660 | this.files[file][id] = this.files[file][id] || [];
661 |
662 | const ruleCache = this.files[file][id];
663 | if (ruleCache.some((result) =>
664 | result.line == line && result.column == column && result.message == message)) {
665 | return false;
666 | }
667 |
668 | ruleCache.push({
669 | line: line,
670 | column: column,
671 | message: message
672 | });
673 |
674 | return true;
675 | };
676 | };
677 |
678 | function combineSarif(resultPath, sarifFiles) {
679 | const resultCache = new ResultCache();
680 | const combinedSarif = {
681 | "version": "2.1.0",
682 | "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json",
683 | "runs": [{
684 | "tool": null,
685 | "results": []
686 | }]
687 | };
688 |
689 | for (const sarifFile of sarifFiles) {
690 | const sarifLog = parseReplyFile(sarifFile);
691 | for (const run of sarifLog.runs) {
692 | if (!combinedSarif.runs[0].tool) {
693 | combinedSarif.runs[0].tool = run.tool;
694 | }
695 |
696 | for (const result of run.results) {
697 | if (resultCache.addIfUnique(result)) {
698 | combinedSarif.runs[0].results.push(result);
699 | }
700 | }
701 | }
702 | }
703 |
704 | try {
705 | fs.writeFileSync(resultPath, JSON.stringify(combinedSarif), 'utf-8');
706 | } catch (err) {
707 | throw new Error("Failed to write combined SARIF result file.", err);
708 | }
709 | }
710 |
711 | /**
712 | * Main
713 | */
714 | async function main() {
715 | var analyzeCommands = [];
716 | try {
717 | const buildDir = resolveInputPath("cmakeBuildDirectory", true);
718 | if (!fs.existsSync(buildDir)) {
719 | throw new Error("CMake build directory does not exist. Ensure CMake is already configured.");
720 | }
721 |
722 | let resultPath = resolveInputPath("resultsPath", false);
723 | if (!resultPath) {
724 | resultPath = path.join(buildDir, "results.sarif");
725 | } else if (!fs.existsSync(path.dirname(resultPath))) {
726 | throw new Error("Directory of the 'resultPath' file must already exist.");
727 | }
728 |
729 | const options = new CompilerCommandOptions();
730 | analyzeCommands = await createAnalysisCommands(buildDir, options);
731 | if (analyzeCommands.length == 0) {
732 | throw new Error('No C/C++ files were found in the project that could be analyzed.');
733 | }
734 |
735 | // TODO: parallelism
736 | const failedSourceFiles = [];
737 | for (const command of analyzeCommands) {
738 | const execOptions = {
739 | cwd: buildDir,
740 | env: command.env,
741 | };
742 |
743 | // TODO: timeouts
744 | core.info(`Running analysis on: ${command.source}`);
745 | try {
746 | await exec.exec(`"${command.compiler}"`, command.args, execOptions);
747 | } catch (err) {
748 | core.debug(`Compilation failed with error: ${err}`);
749 | core.debug("Environment:");
750 | core.debug(execOptions.env);
751 | failedSourceFiles.push(command.source);
752 | }
753 | }
754 |
755 | if (failedSourceFiles.length > 0) {
756 | const fileList = failedSourceFiles
757 | .map(file => path.basename(file))
758 | .join(",");
759 | throw new Error(`Analysis failed due to compiler errors in files: ${fileList}`);
760 | }
761 |
762 | const sarifResults = analyzeCommands.map(command => command.sarifLog);
763 | combineSarif(resultPath, sarifResults);
764 | core.setOutput("sarif", resultPath);
765 |
766 | } catch (error) {
767 | if (core.isDebug()) {
768 | core.setFailed(error.stack)
769 | } else {
770 | core.setFailed(error)
771 | }
772 | } finally {
773 | analyzeCommands.map(command => command.sarifLog)
774 | .filter(log => fs.existsSync(log))
775 | .forEach(log => fs.unlinkSync(log));
776 | }
777 | }
778 |
779 | if (require.main === module) {
780 | (async () => {
781 | await main();
782 | })();
783 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "test": "mocha"
4 | },
5 | "dependencies": {
6 | "@actions/core": "^1.9.1",
7 | "@actions/exec": "^1.1.0",
8 | "@actions/io": "^1.1.1",
9 | "chai": "^4.3.4",
10 | "chai-as-promised": "^7.1.1",
11 | "mocha": "^9.0.3",
12 | "rewire": "^5.0.0",
13 | "testdouble": "^3.16.1",
14 | "tmp": "^0.2.1"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/test/cmake-api-tests.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const assert = require("assert");
4 | const chai = require("chai");
5 | const chaiAsPromised = require("chai-as-promised");
6 | const path = require("path");
7 | const rewire = require("rewire");
8 | const td = require("testdouble");
9 |
10 | chai.use(chaiAsPromised);
11 | const expect = chai.expect;
12 | chai.should();
13 |
14 | const cmakeExePath = path.normalize("C:\\path\\to\\cmake.exe");
15 | const cmakeBuildDir = path.normalize("path\\to\\cmake\\build");
16 | const cmakeSrcDir = path.normalize("path\\to\\project\\src");
17 |
18 | const cmakeApiDir = path.join(cmakeBuildDir, path.normalize(".cmake\\api\\v1"));
19 | const cmakeQueryDir = path.join(cmakeApiDir, "query");
20 | const cmakeReplyDir = path.join(cmakeApiDir, "reply");
21 |
22 | const clPath = "C:/VS/root/Tools/MSVC/14.29.30133/bin/Hostx86/x86/cl.exe";
23 | const clInclude = "C:/VS/root/Tools/MSVC/14.29.30133/include";
24 |
25 | const PATHEnv = [
26 | path.dirname(clPath),
27 | path.dirname(cmakeExePath)
28 | ].join(";");
29 |
30 | const cmakeIndexReply = "index-1.json";
31 | const cmakeCacheReply = "cache-1.json";
32 | const cmakeCodemodelReply = "codemodel-1.json";
33 | const cmakeToolchainsReply = "toolchains-1.json";
34 | const cmakeTarget1Reply = "target-1.json";
35 | const cmakeTarget2Reply = "target-2.json";
36 |
37 | let defaultFileContents = {};
38 | defaultFileContents[cmakeIndexReply] = {
39 | "cmake": {
40 | "generator": {
41 | "multiConfig": true,
42 | "name": "Visual Studio 17 2022"
43 | },
44 | "paths": {
45 | "cmake": cmakeExePath
46 | },
47 | "version": {
48 | "string": "3.21.6"
49 | },
50 | },
51 | "reply" : {
52 | "client-fake": {
53 | "invalid": "data"
54 | },
55 | "client-msvc-ca-action" : {
56 | "query.json" : {
57 | "responses": [
58 | { "kind" : "cache", "jsonFile" : cmakeCacheReply },
59 | { "kind" : "codemodel", "jsonFile" : cmakeCodemodelReply },
60 | { "kind" : "toolchains", "jsonFile" : cmakeToolchainsReply }
61 | ]
62 | }
63 | }
64 | }
65 | };
66 |
67 | defaultFileContents[cmakeCodemodelReply] = {
68 | "kind": "codemodel",
69 | "paths": {
70 | "build": cmakeBuildDir,
71 | "source": cmakeSrcDir
72 | },
73 | "configurations" : [
74 | {
75 | "name": "Regular",
76 | "directories": [
77 | { "source": "." },
78 | { "source": "test" }
79 | ],
80 | "targets": [
81 | {
82 | "directoryIndex": 0,
83 | "jsonFile": cmakeTarget1Reply
84 | },
85 | {
86 | "directoryIndex": 0,
87 | "jsonFile": cmakeTarget2Reply
88 | }
89 | ]
90 | },
91 | {
92 | "name": "OnlyTarget2",
93 | "directories": [
94 | { "source": "." }
95 | ],
96 | "targets": [
97 | {
98 | "directoryIndex": 0,
99 | "jsonFile": cmakeTarget2Reply
100 | }
101 | ]
102 | }
103 | ]
104 | };
105 |
106 | const CLangIndex = 0;
107 | const CXXLangIndex = 1;
108 | defaultFileContents[cmakeCacheReply] = {
109 | "kind": "cache",
110 | "entries": [
111 | { // CLangIndex
112 | "name": "CMAKE_C_COMPILER",
113 | "value": clPath
114 | },
115 | { // CXXLangIndex
116 | "name": "CMAKE_CXX_COMPILER",
117 | "value": clPath
118 | }
119 | ]
120 | };
121 |
122 | defaultFileContents[cmakeToolchainsReply] = {
123 | "kind": "toolchains",
124 | "toolchains": [
125 | { // CLangIndex
126 | "language": "C",
127 | "compiler" : {
128 | "path": clPath,
129 | "id": "MSVC",
130 | "version": "14.29.30133",
131 | "implicit": {
132 | "includeDirectories": [
133 | clInclude
134 | ]
135 | }
136 | }
137 | },
138 | { // CXXLangIndex
139 | "language": "CXX",
140 | "compiler" : {
141 | "path": clPath,
142 | "id": "MSVC",
143 | "version": "14.29.30133",
144 | "implicit": {
145 | "includeDirectories": [
146 | clInclude
147 | ]
148 | }
149 | }
150 | }
151 |
152 | ]
153 | };
154 |
155 | const sharedArgs = "/a /b /c";
156 | const uniqueArgs = "/e /f";
157 | const totalCompileCommands = 4;
158 | defaultFileContents[cmakeTarget1Reply] = {
159 | "compileGroups": [
160 | {
161 | "compileCommandFragments": [
162 | { "fragment": sharedArgs },
163 | { "fragment": uniqueArgs }
164 | ],
165 | "includes": [
166 | { "path": "regular/include"},
167 | { "path": "external/include", "isSystem": true }
168 | ],
169 | "language": "CXX",
170 | "sourceIndexes": [
171 | 0,
172 | 2
173 | ]
174 | },
175 | {
176 | "compileCommandFragments": [
177 | { "fragment": sharedArgs }
178 | ],
179 | "includes": [
180 | { "path": "regular/include", "isSystem": false },
181 | { "path": "external/include", "isSystem": true }
182 | ],
183 | "language": "C",
184 | "sourceIndexes": [
185 | 1
186 | ]
187 | },
188 | ],
189 | "sources": [
190 | { "path" : "src/file1.cpp"},
191 | { "path" : "src/file2.c"},
192 | { "path" : "src/file3.cxx"},
193 | ]
194 | };
195 |
196 | defaultFileContents[cmakeTarget2Reply] = {
197 | "compileGroups": [
198 | {
199 | "compileCommandFragments": [
200 | { "fragment": sharedArgs }
201 | ],
202 | "includes": [
203 | { "path": "regular/include" },
204 | { "path": "external/include", "isSystem": true }
205 | ],
206 | "defines": [
207 | { "define": "a=b"},
208 | { "define": "c=d"},
209 | ],
210 | "language": "CXX",
211 | "sourceIndexes": [
212 | 0
213 | ]
214 | },
215 | ],
216 | "sources": [
217 | { "path" : "src/file4.cpp"},
218 | ]
219 | };
220 |
221 | describe("CMakeApi", () => {
222 | let action;
223 | let exec;
224 | let fs;
225 | let io;
226 |
227 | let getApiReplyIndex;
228 | let loadCMakeApiReplies;
229 | let loadToolchainMap;
230 | let loadCompileCommands;
231 |
232 | function setReplyContents(filename) {
233 | const filepath = path.join(cmakeReplyDir, filename);
234 | td.when(fs.existsSync(filepath)).thenReturn(true);
235 | td.when(fs.readFileSync(filepath, td.matchers.anything())).thenReturn(
236 | JSON.stringify(defaultFileContents[filename]));
237 | }
238 |
239 | function editReplyContents(filename, editCallback) {
240 | const filepath = path.join(cmakeReplyDir, filename);
241 | let contents = JSON.parse(JSON.stringify(defaultFileContents[filename]));
242 | editCallback(contents);
243 | td.when(fs.readFileSync(filepath, td.matchers.anything())).thenReturn(JSON.stringify(contents));
244 | }
245 |
246 | function validateCompileCommands(compileCommands) {
247 | for (const command of compileCommands) {
248 | command.args.should.contain(sharedArgs);
249 | command.includes.length.should.equal(2);
250 | switch (path.basename(command.source)) {
251 | case "file1.cpp":
252 | case "file3.cxx":
253 | command.args.should.contain(uniqueArgs);
254 | break;
255 | case "file2.c":
256 | break;
257 | case "file4.cpp":
258 | command.defines.should.contain("a=b");
259 | command.defines.should.contain("c=d");
260 | break;
261 | default:
262 | assert.fail("Unknown source file: " + compileCommand.source);
263 | }
264 | }
265 | }
266 |
267 | beforeEach(() => {
268 | // modules
269 | td.replace('@actions/core');
270 | exec = td.replace('@actions/exec');
271 | fs = td.replace("fs");
272 | io = td.replace('@actions/io');
273 | action = rewire("../index.js");
274 |
275 | getApiReplyIndex = action.__get__("getApiReplyIndex");
276 | loadCMakeApiReplies = action.__get__("loadCMakeApiReplies");
277 | loadToolchainMap = action.__get__("loadToolchainMap");
278 | loadCompileCommands = action.__get__("loadCompileCommands");
279 |
280 | // default cmake folders
281 | td.when(fs.existsSync(cmakeBuildDir)).thenReturn(true);
282 | td.when(fs.existsSync(cmakeSrcDir)).thenReturn(true);
283 | td.when(fs.existsSync(cmakeApiDir)).thenReturn(true);
284 | td.when(fs.existsSync(cmakeQueryDir)).thenReturn(true);
285 | td.when(fs.existsSync(cmakeReplyDir)).thenReturn(true);
286 | // cmakeBuildDir must be non-empty
287 | td.when(fs.readdirSync(cmakeBuildDir)).thenReturn([".cmake"]);
288 |
289 | // cmake discoverable and successfully executable
290 | td.when(io.which("cmake", true)).thenResolve(cmakeExePath);
291 | td.when(exec.exec("cmake", [cmakeBuildDir])).thenResolve(0);
292 |
293 | // default MSVC toolset
294 | td.when(fs.existsSync(clPath)).thenReturn(true);
295 | td.when(fs.existsSync(clInclude)).thenReturn(true);
296 |
297 | // default reply files
298 | td.when(fs.readdirSync(cmakeReplyDir)).thenReturn(Object.keys(defaultFileContents));
299 | for (const filename of Object.keys(defaultFileContents)) {
300 | setReplyContents(filename);
301 | }
302 | });
303 |
304 | afterEach(() => {
305 | td.reset();
306 | });
307 |
308 | // Common tests.
309 | it("loadCompileCommands", async () => {
310 | const replyIndexInfo = getApiReplyIndex(cmakeApiDir);
311 | const compileCommands = loadCompileCommands(replyIndexInfo, "Regular", []);
312 | validateCompileCommands(compileCommands);
313 | compileCommands.length.should.equal(totalCompileCommands);
314 | });
315 |
316 | it("exclude targets", async () => {
317 | const replyIndexInfo = getApiReplyIndex(cmakeApiDir);
318 | const compileCommands = loadCompileCommands(replyIndexInfo, "Regular", [cmakeSrcDir]);
319 | validateCompileCommands(compileCommands);
320 | compileCommands.length.should.equal(0);
321 | });
322 |
323 | it("filter commands with config", async () => {
324 | const replyIndexInfo = getApiReplyIndex(cmakeApiDir);
325 | const compileCommands = loadCompileCommands(replyIndexInfo, "OnlyTarget2", []);
326 | validateCompileCommands(compileCommands);
327 | compileCommands.length.should.equal(1);
328 | });
329 |
330 | it("loadToolchainMap", async () => {
331 | const replyIndexInfo = getApiReplyIndex(cmakeApiDir);
332 | const toolchainMap = loadToolchainMap(replyIndexInfo);
333 | toolchainMap.should.have.keys(["C", "CXX"]);
334 | });
335 |
336 | // only testing user errors, assume format of query/reply files is valid
337 | describe("errors", () => {
338 | it("empty buildRoot", async () => {
339 | await expect(loadCMakeApiReplies("")).to.be.rejectedWith(
340 | "CMake build root must exist, be non-empty and be configured with CMake");
341 | });
342 |
343 | it("buildRoot does not exist", async () => {
344 | td.when(fs.existsSync(cmakeBuildDir)).thenReturn(false);
345 | await expect(loadCMakeApiReplies(cmakeBuildDir)).to.be.rejectedWith(
346 | "CMake build root must exist, be non-empty and be configured with CMake");
347 | });
348 |
349 | it("cmake not on path", async () => {
350 | td.when(io.which("cmake", true)).thenReject(new Error("cmake missing"));
351 | await expect(loadCMakeApiReplies(cmakeBuildDir)).to.be.rejectedWith("cmake missing");
352 | });
353 |
354 | it("cmake.exe failed to run", async () => {
355 | td.when(exec.exec("cmake", td.matchers.anything())).thenReject(new Error());
356 | await expect(loadCMakeApiReplies(cmakeBuildDir)).to.be.rejectedWith(
357 | "CMake failed to reconfigure project with error:");
358 | });
359 |
360 | it("cmake not run (missing .cmake/api dir)", async () => {
361 | td.when(fs.existsSync(cmakeReplyDir)).thenReturn(false);
362 | await expect(loadCMakeApiReplies(cmakeBuildDir)).to.be.rejectedWith(
363 | "Failed to find CMake API index reply file.");
364 | });
365 |
366 | it("cmake version < 3.20.5", async () => {
367 | editReplyContents(cmakeIndexReply, (reply) => {
368 | reply.cmake.version.string = "3.20.4";
369 | });
370 | await expect(loadCMakeApiReplies(cmakeBuildDir)).to.be.rejectedWith(
371 | "Action requires CMake version >= 3.20.5");
372 | });
373 |
374 | it("msvc for neither C/C++", async () => {
375 | editReplyContents(cmakeToolchainsReply, (reply) => {
376 | reply.toolchains[CLangIndex].compiler.path = "clang.exe";
377 | reply.toolchains[CLangIndex].compiler.id = "Clang";
378 | reply.toolchains[CXXLangIndex].compiler.path = "clang.exe";
379 | reply.toolchains[CXXLangIndex].compiler.id = "Clang";
380 | });
381 |
382 | const replyIndexInfo = getApiReplyIndex(cmakeApiDir);
383 | expect(() => loadToolchainMap(replyIndexInfo)).to.throw(
384 | "Action requires use of MSVC for either/both C or C++.");
385 | });
386 |
387 | it("no configuration (multi-config)", async () => {
388 | const replyIndexInfo = getApiReplyIndex(cmakeApiDir);
389 | expect(() => loadCompileCommands(replyIndexInfo, undefined, [])).to.throw(
390 | "buildConfiguration is required for multi-config CMake Generators.");
391 | });
392 |
393 | it("invalid configuration (multi-config)", async () => {
394 | const replyIndexInfo = getApiReplyIndex(cmakeApiDir);
395 | expect(() => loadCompileCommands(replyIndexInfo, "InvalidConfig", [])).to.throw(
396 | "buildConfiguration does not match any available in CMake project.");
397 | });
398 |
399 | it("invalid configuration (single-config", async () => {
400 | editReplyContents(cmakeCodemodelReply, (reply) => {
401 | reply.configurations.pop(); // Remove second entry, leaving 1
402 | });
403 | const replyIndexInfo = getApiReplyIndex(cmakeApiDir);
404 | expect(() => loadCompileCommands(replyIndexInfo, "InvalidConfig", [])).to.throw(
405 | "buildConfiguration does not match 'Regular' configuration used by CMake.");
406 | });
407 | });
408 | });
--------------------------------------------------------------------------------
/test/sample/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required (VERSION 3.20.5)
2 | project (Sample)
3 |
4 | add_library (${PROJECT_NAME}
5 | src/fileA.cpp
6 | src/fileb.cpp
7 | )
8 |
9 | set_target_properties(${PROJECT_NAME}
10 | PROPERTIES
11 | CXX_STANDARD 20
12 | CXX_STANDARD_REQUIRED ON
13 | CXX_EXTENSIONS OFF
14 | )
15 |
16 | target_include_directories (${PROJECT_NAME}
17 | SYSTEM PUBLIC
18 | ${CMAKE_CURRENT_SOURCE_DIR}/include/system
19 | )
20 |
21 | target_include_directories (${PROJECT_NAME}
22 | PUBLIC
23 | ${CMAKE_CURRENT_SOURCE_DIR}/include/regular
24 | )
25 |
26 |
27 | add_subdirectory(failure)
--------------------------------------------------------------------------------
/test/sample/Custom.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/sample/Invalid.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/sample/failure/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | set(TARGET_NAME Failure)
2 |
3 | add_library (${TARGET_NAME}
4 | fail.cpp
5 | )
6 |
7 | set_target_properties(${TARGET_NAME}
8 | PROPERTIES
9 | CXX_STANDARD 20
10 | CXX_STANDARD_REQUIRED ON
11 | CXX_EXTENSIONS OFF
12 | )
--------------------------------------------------------------------------------
/test/sample/failure/fail.cpp:
--------------------------------------------------------------------------------
1 | // This file is design to purposfully fail compilation because it should be ignored by the
2 | // 'ignoredTargetPaths' action parameter
3 |
4 | invalid C++
--------------------------------------------------------------------------------
/test/sample/include/regular/regular.h:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | // Duplicate warnings will be avoided in headers included > 1 times
4 | const char *regularHeaderFunction()
5 | {
6 | std::string s;
7 | return s.c_str(); // C26816
8 | }
--------------------------------------------------------------------------------
/test/sample/include/system/system.h:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | // Marked as System header in CMake
4 | // No warning should be issued if ignoreSystemHeaders is used
5 | const char *systemHeaderFunction()
6 | {
7 | std::string s;
8 | return s.c_str(); // C26816
9 | }
--------------------------------------------------------------------------------
/test/sample/src/fileA.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | std::optional getTempOptional() noexcept { return {}; }
6 |
7 | void C26815() noexcept
8 | {
9 | if (const auto val = *getTempOptional()) // C26815
10 | {
11 | (void)val;
12 | }
13 | }
14 |
15 | int main() noexcept {
16 | return 0;
17 | }
--------------------------------------------------------------------------------
/test/sample/src/fileB.cpp:
--------------------------------------------------------------------------------
1 | // Include the same header as fileA to ensure duplicate warnings are not produced
2 | #include
3 |
4 | constexpr void C6001()
5 | {
6 | int x[4];
7 | x[4] = 1;
8 | }
9 |
10 | int main() noexcept {
11 | return 0;
12 | }
13 |
--------------------------------------------------------------------------------