update electron to v43

This commit is contained in:
olcxja 2026-07-09 22:38:33 +02:00
commit fb6c8b6ee9
5385 changed files with 513060 additions and 123058 deletions

View file

@ -1,52 +0,0 @@
<!--
Thank you for reporting an issue!
Remember, this issue tracker is for reporting issues ONLY with node-gyp.
If you have an issue installing a specific module, please file an issue on
that module's issue tracker (`npm issues modulename`). Open issue here only if
you are sure this is an issue with node-gyp, not with the module you are
trying to build.
Fill out the form below. We probably won't investigate an issue that does not
provide the basic information we require.
-->
Please look thru your error log for the string `gyp info using node-gyp@` and if the version number is less than the [current release of node-gyp](https://github.com/nodejs/node-gyp/releases) then __please upgrade__ using the instructions at https://github.com/nodejs/node-gyp/blob/master/docs/Updating-npm-bundled-node-gyp.md and try your command again.
Requests for help with [`node-sass` are very common](https://github.com/nodejs/node-gyp/issues?q=label%3A%22Node+Sass+--%3E+Dart+Sass%22). Please be aware that this package is deprecated, you should seek alternatives and avoid opening new issues about it here.
* **Node Version**: <!-- `node -v` and `npm -v` -->
* **Platform**: <!-- `uname -a` (UNIX), or `systeminfo | findstr /B /C:"OS Name" /C:"OS Version" /C:"System Type"` (Windows) -->
* **Compiler**: <!-- `cc -v` (UNIX) or `msbuild /version & cl` (Windows) -->
* **Module**: <!-- what you tried to build/install -->
<details><summary>Verbose output (from npm or node-gyp):</summary>
```
Paste your log here, between the backticks. It can be:
- npm --verbose output,
- or contents of npm-debug.log,
- or output of node-gyp rebuild --verbose.
Include the command you were trying to run.
This should look like this:
>npm --verbose
npm info it worked if it ends with ok
npm verb cli [
npm verb cli 'C:\\...\\node\\13.9.0\\x64\\node.exe',
npm verb cli 'C:\\...\\node\\13.9.0\\x64\\node_modules\\npm\\bin\\npm-cli.js',
npm verb cli '--verbose'
npm verb cli ]
npm info using npm@6.13.7
npm info using node@v13.9.0
Usage: npm <command>
(...)
```
</details>
<!-- Any further details -->

View file

@ -1,17 +0,0 @@
<!--
Thank you for your pull request. Please review the below requirements.
Contributor guide: https://github.com/nodejs/node/blob/master/CONTRIBUTING.md
-->
##### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->
- [ ] `npm install && npm test` passes
- [ ] tests are included <!-- Bug fixes and new features should include tests -->
- [ ] documentation is changed or added
- [ ] commit message follows [commit guidelines](https://github.com/googleapis/release-please#how-should-i-write-my-commits)
##### Description of change
<!-- Provide a description of the change -->

View file

@ -1,57 +0,0 @@
name: release-please
on:
push:
branches:
- release/v9
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: google-github-actions/release-please-action@v2
id: release
with:
package-name: node-gyp
release-type: node
default-branch: release/v9
changelog-types: >
[{"type":"feat","section":"Features","hidden":false},
{"type":"fix","section":"Bug Fixes","hidden":false},
{"type":"bin","section":"Core","hidden":false},
{"type":"gyp","section":"Core","hidden":false},
{"type":"lib","section":"Core","hidden":false},
{"type":"src","section":"Core","hidden":false},
{"type":"test","section":"Tests","hidden":false},
{"type":"build","section":"Core","hidden":false},
{"type":"clean","section":"Core","hidden":false},
{"type":"configure","section":"Core","hidden":false},
{"type":"install","section":"Core","hidden":false},
{"type":"list","section":"Core","hidden":false},
{"type":"rebuild","section":"Core","hidden":false},
{"type":"remove","section":"Core","hidden":false},
{"type":"deps","section":"Core","hidden":false},
{"type":"python","section":"Core","hidden":false},
{"type":"lin","section":"Core","hidden":false},
{"type":"linux","section":"Core","hidden":false},
{"type":"mac","section":"Core","hidden":false},
{"type":"macos","section":"Core","hidden":false},
{"type":"win","section":"Core","hidden":false},
{"type":"windows","section":"Core","hidden":false},
{"type":"zos","section":"Core","hidden":false},
{"type":"doc","section":"Doc","hidden":false},
{"type":"docs","section":"Doc","hidden":false},
{"type":"readme","section":"Doc","hidden":false},
{"type":"chore","section":"Miscellaneous","hidden":false},
{"type":"refactor","section":"Miscellaneous","hidden":false},
{"type":"ci","section":"Miscellaneous","hidden":false},
{"type":"meta","section":"Miscellaneous","hidden":false}]
# Standard Conventional Commits: `feat` and `fix`
# node-gyp subdirectories: `bin`, `gyp`, `lib`, `src`, `test`
# node-gyp subcommands: `build`, `clean`, `configure`, `install`, `list`, `rebuild`, `remove`
# Core abstract category: `deps`
# Languages/platforms: `python`, `lin`, `linux`, `mac`, `macos`, `win`, `window`, `zos`
# Documentation: `doc`, `docs`, `readme`
# Standard Conventional Commits: `chore` (under "Miscellaneous")
# Miscellaneous abstract categories: `refactor`, `ci`, `meta`

View file

@ -1,56 +0,0 @@
name: release-please
on:
push:
branches:
- main
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: google-github-actions/release-please-action@v2
id: release
with:
package-name: node-gyp
release-type: node
changelog-types: >
[{"type":"feat","section":"Features","hidden":false},
{"type":"fix","section":"Bug Fixes","hidden":false},
{"type":"bin","section":"Core","hidden":false},
{"type":"gyp","section":"Core","hidden":false},
{"type":"lib","section":"Core","hidden":false},
{"type":"src","section":"Core","hidden":false},
{"type":"test","section":"Tests","hidden":false},
{"type":"build","section":"Core","hidden":false},
{"type":"clean","section":"Core","hidden":false},
{"type":"configure","section":"Core","hidden":false},
{"type":"install","section":"Core","hidden":false},
{"type":"list","section":"Core","hidden":false},
{"type":"rebuild","section":"Core","hidden":false},
{"type":"remove","section":"Core","hidden":false},
{"type":"deps","section":"Core","hidden":false},
{"type":"python","section":"Core","hidden":false},
{"type":"lin","section":"Core","hidden":false},
{"type":"linux","section":"Core","hidden":false},
{"type":"mac","section":"Core","hidden":false},
{"type":"macos","section":"Core","hidden":false},
{"type":"win","section":"Core","hidden":false},
{"type":"windows","section":"Core","hidden":false},
{"type":"zos","section":"Core","hidden":false},
{"type":"doc","section":"Doc","hidden":false},
{"type":"docs","section":"Doc","hidden":false},
{"type":"readme","section":"Doc","hidden":false},
{"type":"chore","section":"Miscellaneous","hidden":false},
{"type":"refactor","section":"Miscellaneous","hidden":false},
{"type":"ci","section":"Miscellaneous","hidden":false},
{"type":"meta","section":"Miscellaneous","hidden":false}]
# Standard Conventional Commits: `feat` and `fix`
# node-gyp subdirectories: `bin`, `gyp`, `lib`, `src`, `test`
# node-gyp subcommands: `build`, `clean`, `configure`, `install`, `list`, `rebuild`, `remove`
# Core abstract category: `deps`
# Languages/platforms: `python`, `lin`, `linux`, `mac`, `macos`, `win`, `window`, `zos`
# Documentation: `doc`, `docs`, `readme`
# Standard Conventional Commits: `chore` (under "Miscellaneous")
# Miscellaneous abstract categories: `refactor`, `ci`, `meta`

View file

@ -1,59 +0,0 @@
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
# TODO: Line 48, enable pytest --doctest-modules
name: Tests
on:
push:
branches:
- main
- release/v*
pull_request:
branches:
- main
- release/v*
jobs:
Lint_Python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install --user ruff
- run: ruff --output-format=github --select="E,F,PLC,PLE,UP,W,YTT" --ignore="E721,PLC1901,S101,UP031" --target-version=py38 .
Tests:
strategy:
fail-fast: false
max-parallel: 15
matrix:
node: [16.x, 18.x, 20.x]
python: ["3.8", "3.11"]
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- name: Use Python ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
env:
PYTHON_VERSION: ${{ matrix.python }} # Why do this?
- name: Install Dependencies
run: |
npm install --no-progress
pip install pytest
- name: Set Windows environment
if: startsWith(matrix.os, 'windows')
run: |
echo 'GYP_MSVS_VERSION=2015' >> $Env:GITHUB_ENV
echo 'GYP_MSVS_OVERRIDE_PATH=C:\\Dummy' >> $Env:GITHUB_ENV
- name: Run Python tests
run: python -m pytest
# - name: Run doctests with pytest
# run: python -m pytest --doctest-modules
- name: Environment Information
run: npx envinfo
- name: Run Node tests
run: npm test

View file

@ -1,37 +0,0 @@
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
name: visual-studio
on:
push:
branches:
- main
- release/v*
pull_request:
branches:
- main
- release/v*
jobs:
visual-studio:
strategy:
fail-fast: false
max-parallel: 8
matrix:
os: [windows-latest]
msvs-version: [2016, 2019, 2022] # https://github.com/actions/virtual-environments/tree/main/images/win
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Install Dependencies
run: |
npm install --no-progress
# npm audit fix --force
- name: Set Windows environment
if: startsWith(matrix.os, 'windows')
run: |
echo 'GYP_MSVS_VERSION=${{ matrix.msvs-version }}' >> $Env:GITHUB_ENV
echo 'GYP_MSVS_OVERRIDE_PATH=C:\\Dummy' >> $Env:GITHUB_ENV
- name: Environment Information
run: npx envinfo
- name: Run Node tests
run: npm test

View file

@ -0,0 +1,3 @@
{
".": "12.4.0"
}

View file

@ -1,23 +1,417 @@
# Changelog
### [9.4.1](https://www.github.com/nodejs/node-gyp/compare/v9.4.0...v9.4.1) (2023-10-27)
## [12.4.0](https://github.com/nodejs/node-gyp/compare/v12.3.0...v12.4.0) (2026-05-15)
### Features
* update gyp-next to v0.22.2 ([#3316](https://github.com/nodejs/node-gyp/issues/3316)) ([8ea71e5](https://github.com/nodejs/node-gyp/commit/8ea71e5a0d5577f8f780df7597fa1f94089e1be4))
### Bug Fixes
* Revert "update make-fetch-happen to 11.0.3 ([#2796](https://www.github.com/nodejs/node-gyp/issues/2796))" ([82c2d64](https://www.github.com/nodejs/node-gyp/commit/82c2d64ca8288853c79c6c42b9e49a64ebd21d86))
### Tests
* update expired certs ([#2908](https://www.github.com/nodejs/node-gyp/issues/2908)) ([cd1b637](https://www.github.com/nodejs/node-gyp/commit/cd1b637aa4e5d97302d673a789cfee3c44f74aa3))
* retry downloads on retryable errors ([#3308](https://github.com/nodejs/node-gyp/issues/3308)) ([0793489](https://github.com/nodejs/node-gyp/commit/0793489b961c2cbf6c6b2182d968d0c262c3b573))
* stop testing end-of-life Node.js v20 ([#3315](https://github.com/nodejs/node-gyp/issues/3315)) ([5c0ec47](https://github.com/nodejs/node-gyp/commit/5c0ec47797a18d3e65f0c4c83e5812289191cfe1))
* test on Node.js v26 ([#3314](https://github.com/nodejs/node-gyp/issues/3314)) ([0e65639](https://github.com/nodejs/node-gyp/commit/0e65639baa8b64b5fecec3820a4f07dc7c4a42ad))
### Miscellaneous
* add release branches as ci targets ([b68da05](https://www.github.com/nodejs/node-gyp/commit/b68da055ed48c58c6cd614232f0e5bfa96ef7b7b))
* add release-please to v9 branch ([5a65d03](https://www.github.com/nodejs/node-gyp/commit/5a65d03728b4b42b90912169917ba50be179463f))
* increase parallel install timeouts ([fd77b34](https://www.github.com/nodejs/node-gyp/commit/fd77b346a9b5cf7a0bddc237ad71b45deb5d31ea))
* add workflow_dispatch trigger to tests workflow ([#3299](https://github.com/nodejs/node-gyp/issues/3299)) ([b2fcdcd](https://github.com/nodejs/node-gyp/commit/b2fcdcdd6aebdf0ab6d7d296fcabccf188883e01))
## [12.3.0](https://github.com/nodejs/node-gyp/compare/v12.2.0...v12.3.0) (2026-04-21)
### Features
* replace make-fetch-happen with built-in fetch ([#3302](https://github.com/nodejs/node-gyp/issues/3302)) ([393ec2b](https://github.com/nodejs/node-gyp/commit/393ec2be480195b585768eb18ac2d92b858b12db))
* update gyp-next to v0.22.1 ([#3295](https://github.com/nodejs/node-gyp/issues/3295)) ([f4242fb](https://github.com/nodejs/node-gyp/commit/f4242fb7bf7592d71848bae5c7f8597f2718dc3f))
### Bug Fixes
* Switch to URL instead of url.resolve ([#3256](https://github.com/nodejs/node-gyp/issues/3256)) ([#3263](https://github.com/nodejs/node-gyp/issues/3263)) ([46d7576](https://github.com/nodejs/node-gyp/commit/46d75768018d10dc4b5693b35cf58f502425cbbe))
### Core
* **deps-dev:** bump neostandard from 0.12.2 to 0.13.0 ([#3289](https://github.com/nodejs/node-gyp/issues/3289)) ([19da158](https://github.com/nodejs/node-gyp/commit/19da1583b3876dce8c97263b168d9dfef637b76b))
* improve Add-Type with -IgnoreWarnings ([#3280](https://github.com/nodejs/node-gyp/issues/3280)) ([1381458](https://github.com/nodejs/node-gyp/commit/13814583a476c85a84d5ac902c7ffa310120cb88))
## [12.2.0](https://github.com/nodejs/node-gyp/compare/v12.1.0...v12.2.0) (2026-01-26)
### Features
* include built package version in error logs ([#3254](https://github.com/nodejs/node-gyp/issues/3254)) ([ee9cbdd](https://github.com/nodejs/node-gyp/commit/ee9cbdd6e1d40dc7c1cdc5ed6a75432c716eaf3f))
* update gyp-next to v0.21.1 ([#3273](https://github.com/nodejs/node-gyp/issues/3273)) ([888ff2c](https://github.com/nodejs/node-gyp/commit/888ff2c48a4cf5602013b96b52c6670906976f63))
### Bug Fixes
* cpu concurrency detection on some platforms ([#3255](https://github.com/nodejs/node-gyp/issues/3255)) ([f15b79a](https://github.com/nodejs/node-gyp/commit/f15b79a03c54cea0f66d940a0d6d839df867a319)), closes [#3191](https://github.com/nodejs/node-gyp/issues/3191)
* python is no longer a valid npm config setting ([#3258](https://github.com/nodejs/node-gyp/issues/3258)) ([c7c678f](https://github.com/nodejs/node-gyp/commit/c7c678f89837d956194f326b01c5a8eb1d745db3))
* Switch to URL instead of url.parse ([#3256](https://github.com/nodejs/node-gyp/issues/3256)) ([3f81949](https://github.com/nodejs/node-gyp/commit/3f819499d8ce6d46c646466de7b9492bf7bde663))
* Test Windows on Python 3.14, not 3.13 ([#3262](https://github.com/nodejs/node-gyp/issues/3262)) ([7b4f315](https://github.com/nodejs/node-gyp/commit/7b4f315e4dad880c841d21df641d6dd9b68bf36b))
### Core
* **deps:** bump actions/checkout from 5 to 6 ([#3248](https://github.com/nodejs/node-gyp/issues/3248)) ([db5385c](https://github.com/nodejs/node-gyp/commit/db5385c5467e5bfb914b9954f0313c46f1f4e10d))
### Doc
* add a note about changes in gyp folder ([#3259](https://github.com/nodejs/node-gyp/issues/3259)) ([a52bc81](https://github.com/nodejs/node-gyp/commit/a52bc819f44b881854ff798865ad416430e3dce2))
* correct typos ([#3269](https://github.com/nodejs/node-gyp/issues/3269)) ([0f2bc7d](https://github.com/nodejs/node-gyp/commit/0f2bc7d2e0665b1c7bb03e1cd8653ea330277a70))
* remove obsolete Microsoft Node.js Guidelines link ([#3268](https://github.com/nodejs/node-gyp/issues/3268)) ([30cda26](https://github.com/nodejs/node-gyp/commit/30cda268730798dc0f67182c8c568d8b8069964e))
* update Python manual install instructions for Windows ([#3265](https://github.com/nodejs/node-gyp/issues/3265)) ([0407877](https://github.com/nodejs/node-gyp/commit/0407877e3e26d3201f74cf1a9deabbbfc40bdbb7))
### Miscellaneous
* **deps:** upgrade tar to 7.5.4 to address CVE-2026-23950 ([#3271](https://github.com/nodejs/node-gyp/issues/3271)) ([7bf371c](https://github.com/nodejs/node-gyp/commit/7bf371c4dd7c694232ab3169d02fe8197e1ecc6d))
## [12.1.0](https://github.com/nodejs/node-gyp/compare/v12.0.0...v12.1.0) (2025-11-12)
### Features
* Add support for Visual Studio 2026 (18.x) ([69e5fd2](https://github.com/nodejs/node-gyp/commit/69e5fd2c98ac83dad5200a47515b301ccd80d2d3))
* Support for Visual Studio 2026 (18.x) ([69e5fd2](https://github.com/nodejs/node-gyp/commit/69e5fd2c98ac83dad5200a47515b301ccd80d2d3))
## [12.0.0](https://github.com/nodejs/node-gyp/compare/v11.5.0...v12.0.0) (2025-11-10)
### ⚠ BREAKING CHANGES
* align to npm 11 node engine range
### Features
* align to npm 11 node engine range ([2f85686](https://github.com/nodejs/node-gyp/commit/2f85686bbe745673350a8f9dbb0e86ee0190f213))
* update gyp-next to v0.21.0 ([c57cd2e](https://github.com/nodejs/node-gyp/commit/c57cd2e86dc57707475b9f7e676e189f064817de))
### Core
* **deps:** bump actions/setup-node from 5 to 6 ([ae90e63](https://github.com/nodejs/node-gyp/commit/ae90e632d9fab85f4cd902dc9205ba9dfafaf3bc))
* **deps:** bump env-paths from 2.2.1 to 3.0.0 ([#3235](https://github.com/nodejs/node-gyp/issues/3235)) ([5fffb2f](https://github.com/nodejs/node-gyp/commit/5fffb2ffee304cc898fdea7a0cd9e41d54c53839))
* **deps:** bump which from 5.0.0 to 6.0.0 ([#3238](https://github.com/nodejs/node-gyp/issues/3238)) ([eaa8e34](https://github.com/nodejs/node-gyp/commit/eaa8e34cb5a0710bef0602c42e5840b47eb76822))
* make-fetch-happen@15.0.0 ([e2b9d21](https://github.com/nodejs/node-gyp/commit/e2b9d21bce27c35d18fcb6f8583e386d15ce395c))
* nopt@9.0.0 ([9bdeaf3](https://github.com/nodejs/node-gyp/commit/9bdeaf307cd7a254946859d306465989fa39dfb2))
* proc-log@6.0.0 ([dfc68df](https://github.com/nodejs/node-gyp/commit/dfc68dfba3c17deb0bda9a395bb49d8fb9fa5951))
### Miscellaneous
* increase test timeouts ([#3237](https://github.com/nodejs/node-gyp/issues/3237)) ([3b41971](https://github.com/nodejs/node-gyp/commit/3b41971e2f6b90e02b1d7df592d403b8dfc8fa4d))
* setup dependabot for npm ([86d65c7](https://github.com/nodejs/node-gyp/commit/86d65c7874eb41eb49c9b8bbf342becac8e57c6f))
* update devDependencies ([41b0cea](https://github.com/nodejs/node-gyp/commit/41b0cea2f12342a790580cc8f844f075d49e096c))
## [11.5.0](https://github.com/nodejs/node-gyp/compare/v11.4.2...v11.5.0) (2025-10-15)
### Features
* update gyp-next to v0.20.5 ([#3222](https://github.com/nodejs/node-gyp/issues/3222)) ([848e950](https://github.com/nodejs/node-gyp/commit/848e950833b90f0b25f346710ee42e9be4797604))
### Bug Fixes
* **ci:** Run Visual Studio test on Windows 11 on ARM ([#3217](https://github.com/nodejs/node-gyp/issues/3217)) ([8bd3f63](https://github.com/nodejs/node-gyp/commit/8bd3f6354b8bd43262a4d99d58a568beab0459e8))
* **ci:** Test on Python 3.14 release candidate 3 on Linux and macOS ([#3216](https://github.com/nodejs/node-gyp/issues/3216)) ([085b445](https://github.com/nodejs/node-gyp/commit/085b445d1c00f8f1fc6a6ff80d8a93c6643f11ee))
### Core
* **deps:** bump actions/github-script from 7 to 8 ([#3213](https://github.com/nodejs/node-gyp/issues/3213)) ([c6b968c](https://github.com/nodejs/node-gyp/commit/c6b968caf7f4e22687fc10716162675b1411f713))
* **deps:** bump actions/setup-node from 4 to 5 ([#3211](https://github.com/nodejs/node-gyp/issues/3211)) ([921c04d](https://github.com/nodejs/node-gyp/commit/921c04d142549f172d3aeae4097c9e0af05599dd))
* **deps:** bump actions/setup-python from 5 to 6 ([#3210](https://github.com/nodejs/node-gyp/issues/3210)) ([6b70b05](https://github.com/nodejs/node-gyp/commit/6b70b05ed21cb977214348c97c2b97515c0d08f3))
## [11.4.2](https://github.com/nodejs/node-gyp/compare/v11.4.1...v11.4.2) (2025-08-26)
### Bug Fixes
* add adaptation for OpenHarmony platform ([#3207](https://github.com/nodejs/node-gyp/issues/3207)) ([b406532](https://github.com/nodejs/node-gyp/commit/b406532c77659c441c845708ec3ecdf09f013a3b))
### Miscellaneous
* update gyp-next to v0.20.4 ([#3208](https://github.com/nodejs/node-gyp/issues/3208)) ([adc61b1](https://github.com/nodejs/node-gyp/commit/adc61b1458315d9648591e74bf16bbe39511401e))
* **ci:** Update Node.js version matrix in `tests.yml` ([#3209](https://github.com/nodejs/node-gyp/issues/3209)) ([a4e1da6](https://github.com/nodejs/node-gyp/commit/a4e1da6683a37fde565e1ea50f1fa86fa99a83c7))
* ruff format Python code ([#3203](https://github.com/nodejs/node-gyp/issues/3203)) ([cb30a53](https://github.com/nodejs/node-gyp/commit/cb30a538eadf49ca0310980ffb0bfdb8fcebf0a4))
## [11.4.1](https://github.com/nodejs/node-gyp/compare/v11.4.0...v11.4.1) (2025-08-20)
### Miscellaneous
* **release:** use npm@11 for OIDC publishing ([#3202](https://github.com/nodejs/node-gyp/issues/3202)) ([6b9638a](https://github.com/nodejs/node-gyp/commit/6b9638a0f80352e5bf7c1702e6ef622a6474d44a)), closes [#3201](https://github.com/nodejs/node-gyp/issues/3201)
## [11.4.0](https://github.com/nodejs/node-gyp/compare/v11.3.0...v11.4.0) (2025-08-19)
### Features
* read from config case-insensitively ([#3198](https://github.com/nodejs/node-gyp/issues/3198)) ([5538e6c](https://github.com/nodejs/node-gyp/commit/5538e6c5d78dffd41e2a588adfa7ea9022150b9d))
* support reading config from package.json ([#3196](https://github.com/nodejs/node-gyp/issues/3196)) ([1822dff](https://github.com/nodejs/node-gyp/commit/1822dff4f616a30ac3ca72e5946d81389cb8557e)), closes [#3156](https://github.com/nodejs/node-gyp/issues/3156)
### Core
* **deps:** bump actions/checkout from 4 to 5 ([#3193](https://github.com/nodejs/node-gyp/issues/3193)) ([27f5505](https://github.com/nodejs/node-gyp/commit/27f5505ec236551081366bf8a9c13ef5d8e468bf))
### Miscellaneous
* use npm oicd connection for publishing ([#3197](https://github.com/nodejs/node-gyp/issues/3197)) ([0773615](https://github.com/nodejs/node-gyp/commit/077361502933fcb994ca365c3c07c03177503df2))
## [11.3.0](https://github.com/nodejs/node-gyp/compare/v11.2.0...v11.3.0) (2025-07-29)
### Features
* update gyp-next to v0.20.2 ([#3169](https://github.com/nodejs/node-gyp/issues/3169)) ([0e65632](https://github.com/nodejs/node-gyp/commit/0e656322c1e94041331ab3b01bf66c2ef9bd6ead))
### Bug Fixes
* Correct Visual Studio 2019 test version ([#3153](https://github.com/nodejs/node-gyp/issues/3153)) ([7d883b5](https://github.com/nodejs/node-gyp/commit/7d883b5cf4c26e76065201f85b0be36d5ebdcc0e))
* Normalize win32 library names ([#3189](https://github.com/nodejs/node-gyp/issues/3189)) ([b81a665](https://github.com/nodejs/node-gyp/commit/b81a665acfb9d88102e8044a8ec8ca74a3e9eccc))
* use temp dir for tar extraction on all platforms ([#3170](https://github.com/nodejs/node-gyp/issues/3170)) ([b41864f](https://github.com/nodejs/node-gyp/commit/b41864f7c1c60e4a160c1b4dd91558dcaa3f74e4)), closes [#3165](https://github.com/nodejs/node-gyp/issues/3165)
### Miscellaneous
* retry wasi-sdk download in CI ([#3151](https://github.com/nodejs/node-gyp/issues/3151)) ([8f3cd8b](https://github.com/nodejs/node-gyp/commit/8f3cd8b3a157bccd8d7110e7d46a27c2926625cd))
* Windows 2019 has been removed from GitHub Actions ([#3190](https://github.com/nodejs/node-gyp/issues/3190)) ([3df8789](https://github.com/nodejs/node-gyp/commit/3df8789a9aa73c60707eec8f02f4e926491d6102))
## [11.2.0](https://github.com/nodejs/node-gyp/compare/v11.1.0...v11.2.0) (2025-04-01)
### Features
* update gyp-next to v0.20.0 ([#3149](https://github.com/nodejs/node-gyp/issues/3149)) ([80e9c79](https://github.com/nodejs/node-gyp/commit/80e9c795a739c490cfbc85633e63022b36a7c70d))
### Bug Fixes
* disable msbuild.exe nodeReuse ([#3112](https://github.com/nodejs/node-gyp/issues/3112)) ([0cf16d2](https://github.com/nodejs/node-gyp/commit/0cf16d29fe604266fb47325496287a63075ea532))
* use maxRetries on fs.rm calls ([#3113](https://github.com/nodejs/node-gyp/issues/3113)) ([a2772a7](https://github.com/nodejs/node-gyp/commit/a2772a76709f939af1e80dd8fe766ca2143aa5bf))
### Tests
* fix wasm test on Windows ([#3145](https://github.com/nodejs/node-gyp/issues/3145)) ([ee1d6fd](https://github.com/nodejs/node-gyp/commit/ee1d6fd8d83c9dd3eae7df7ec533bb6b39e1a812))
* use maxRetries with tests too ([#3150](https://github.com/nodejs/node-gyp/issues/3150)) ([0ccbe7e](https://github.com/nodejs/node-gyp/commit/0ccbe7e90afb096b46a7818ba127a4871237952e))
### Doc
* add ffi-napi to docs/README.md ([#3138](https://github.com/nodejs/node-gyp/issues/3138)) ([4885110](https://github.com/nodejs/node-gyp/commit/48851107ad8c5d2cf18a55e8bd2764f5938e7102))
### Miscellaneous
* switch to tinyglobby ([#3133](https://github.com/nodejs/node-gyp/issues/3133)) ([c3b3ab0](https://github.com/nodejs/node-gyp/commit/c3b3ab06ee0f092cd5c0646120d57e56d41b79fc))
* update tinyglobby ([#3136](https://github.com/nodejs/node-gyp/issues/3136)) ([b21cf87](https://github.com/nodejs/node-gyp/commit/b21cf874f58883f3fd4dd07bec3b584fb07e831d))
## [11.1.0](https://github.com/nodejs/node-gyp/compare/v11.0.0...v11.1.0) (2025-02-10)
### Features
* update gyp-next to v0.19.1 ([#3122](https://github.com/nodejs/node-gyp/issues/3122)) ([504250e](https://github.com/nodejs/node-gyp/commit/504250e5e3e27c6ef6dcfcaa744b36e1a99c1be8))
### Bug Fixes
* Find VC.Tools.ARM64 on arm64 machine ([#3075](https://github.com/nodejs/node-gyp/issues/3075)) ([b899fae](https://github.com/nodejs/node-gyp/commit/b899faed56270d3d8496da7576b5750b264c2c21))
* try libnode.dll first in load_exe_hook ([#2834](https://github.com/nodejs/node-gyp/issues/2834)) ([b9d10a5](https://github.com/nodejs/node-gyp/commit/b9d10a5a37081e2a731937e43eca52c83609e7f5))
### Miscellaneous
* add gyp-next updater ([#3105](https://github.com/nodejs/node-gyp/issues/3105)) ([e3f9a77](https://github.com/nodejs/node-gyp/commit/e3f9a7756f65a7f4e50799017b3dc51d5bc195b2))
* Test on Ubuntu-24.04-arm and Node.js v23 ([#3121](https://github.com/nodejs/node-gyp/issues/3121)) ([2530f51](https://github.com/nodejs/node-gyp/commit/2530f51cec3ba595184e5bcb7fe1245e240beb59))
* Use astral-sh/ruff-action@v3 to run the Python linter ([#3114](https://github.com/nodejs/node-gyp/issues/3114)) ([94448fc](https://github.com/nodejs/node-gyp/commit/94448fcd9f090814bce1c4361471dae199dc2e82))
## [11.0.0](https://github.com/nodejs/node-gyp/compare/v10.3.1...v11.0.0) (2024-12-03)
### ⚠ BREAKING CHANGES
* drop node 16 support ([#3102](https://github.com/nodejs/node-gyp/issues/3102))
### Features
* drop node 16 support ([#3102](https://github.com/nodejs/node-gyp/issues/3102)) ([0e6b6f8](https://github.com/nodejs/node-gyp/commit/0e6b6f8bea615cf031d76ecff9102a38e5474c72))
### Miscellaneous
* migrate from standard to neostandard ([#3103](https://github.com/nodejs/node-gyp/issues/3103)) ([a130178](https://github.com/nodejs/node-gyp/commit/a13017807d0ae7da8fa076b0bcf23153af7c60a6))
## [10.3.1](https://github.com/nodejs/node-gyp/compare/v10.3.0...v10.3.1) (2024-12-02)
### Miscellaneous
* fix npm-publish dependencies and add provenance ([#3099](https://github.com/nodejs/node-gyp/issues/3099)) ([6dded88](https://github.com/nodejs/node-gyp/commit/6dded88065872a32f44114e60731ba4b701ec057))
## [10.3.0](https://github.com/nodejs/node-gyp/compare/v10.2.0...v10.3.0) (2024-11-29)
### Features
* prohibit compiling with ClangCL on Windows ([#3098](https://github.com/nodejs/node-gyp/issues/3098)) ([88260bf](https://github.com/nodejs/node-gyp/commit/88260bf86aeb4c39959b78104a5edc3dc88d3aef))
### Bug Fixes
* **ci:** use correct release-please-action domain after organization url was changed ([#3032](https://github.com/nodejs/node-gyp/issues/3032)) ([d1ed3d4](https://github.com/nodejs/node-gyp/commit/d1ed3d4dc3a53b8ccab4093d002e43945bbece0e))
### Miscellaneous
* add links to Code of Conduct from root file ([#2196](https://github.com/nodejs/node-gyp/issues/2196)) ([d22e2eb](https://github.com/nodejs/node-gyp/commit/d22e2eb080807c6290533a67249c343a7605a989))
* publish to npm with release-please ([#3051](https://github.com/nodejs/node-gyp/issues/3051)) ([8319847](https://github.com/nodejs/node-gyp/commit/831984736393a3ea8417efec5255f95d53a70785))
## [10.2.0](https://github.com/nodejs/node-gyp/compare/v10.1.0...v10.2.0) (2024-07-09)
### Features
* allow VCINSTALLDIR to specify a portable instance ([#3036](https://github.com/nodejs/node-gyp/issues/3036)) ([d38af2e](https://github.com/nodejs/node-gyp/commit/d38af2e0c2a81b12cd221b1f8517fb89e609d62c))
* **gyp:** update gyp to v0.18.1 ([#3039](https://github.com/nodejs/node-gyp/issues/3039)) ([ea99fea](https://github.com/nodejs/node-gyp/commit/ea99fea83485dc5be04db01df9b2fdbe05319b8e))
* support `rebuild` and `build` for cross-compiling Node-API module to wasm on Windows ([#2974](https://github.com/nodejs/node-gyp/issues/2974)) ([6318d2b](https://github.com/nodejs/node-gyp/commit/6318d2b210224415ff5932c2863e6cc14d4583dc))
### Core
* add an arch check to VS 2019 ([#3025](https://github.com/nodejs/node-gyp/issues/3025)) ([323957b](https://github.com/nodejs/node-gyp/commit/323957b74e9586fb3fbfb2acad5040379c778de6))
* **deps:** bump seanmiddleditch/gha-setup-ninja from 4 to 5 ([#3041](https://github.com/nodejs/node-gyp/issues/3041)) ([10f6730](https://github.com/nodejs/node-gyp/commit/10f6730be660e7a38be8a12111937e37fcf74834))
* proc-log@4.0.0 ([#3022](https://github.com/nodejs/node-gyp/issues/3022)) ([141aa6b](https://github.com/nodejs/node-gyp/commit/141aa6bf029e6f984be8ea98aaf985e5df894082))
* tar@6.2.1 ([#3021](https://github.com/nodejs/node-gyp/issues/3021)) ([b22d5ee](https://github.com/nodejs/node-gyp/commit/b22d5eef861892c968052ffc1c71b551f738163b))
### Doc
* `node-pre-gyp` is no longer maintained ([#3015](https://github.com/nodejs/node-gyp/issues/3015)) ([93186f1](https://github.com/nodejs/node-gyp/commit/93186f10c966b4148fc500e48f8cbffacccdfa3c))
* add the way to configuring Python dependency for Windows PowerShell ([#2996](https://github.com/nodejs/node-gyp/issues/2996)) ([9fd7936](https://github.com/nodejs/node-gyp/commit/9fd7936f0d7232a8a79e6a7b6cbfb814d9042b13))
* Installation -- Python >= v3.12 requires `node-gyp` >= v10 ([#3010](https://github.com/nodejs/node-gyp/issues/3010)) ([a6b48fc](https://github.com/nodejs/node-gyp/commit/a6b48fca9993e54d757cd110f6b41f8200d99ca4))
### Miscellaneous
* fix ruff command ([#3044](https://github.com/nodejs/node-gyp/issues/3044)) ([b3916d5](https://github.com/nodejs/node-gyp/commit/b3916d5b25704a53e89be16b500036a14bdc5060))
## [10.1.0](https://github.com/nodejs/node-gyp/compare/v10.0.1...v10.1.0) (2024-03-13)
### Features
* improve visual studio detection ([#2957](https://github.com/nodejs/node-gyp/issues/2957)) ([109e3d4](https://github.com/nodejs/node-gyp/commit/109e3d4245504a7b75c99f578e1203c0ef4b518e))
### Core
* add support for locally installed headers ([#2964](https://github.com/nodejs/node-gyp/issues/2964)) ([3298731](https://github.com/nodejs/node-gyp/commit/329873141f0d3e3787d3c006801431da04e4ed0c))
* **deps:** bump actions/setup-python from 4 to 5 ([#2960](https://github.com/nodejs/node-gyp/issues/2960)) ([3f0df7e](https://github.com/nodejs/node-gyp/commit/3f0df7e9334e49e8c7f6fdbbb9e1e6c5a8cca53b))
* **deps:** bump google-github-actions/release-please-action ([#2961](https://github.com/nodejs/node-gyp/issues/2961)) ([b1f1808](https://github.com/nodejs/node-gyp/commit/b1f1808bfff0d51e6d3eb696ab6a5b89b7b9630c))
* print Python executable path using UTF-8 ([#2995](https://github.com/nodejs/node-gyp/issues/2995)) ([c472912](https://github.com/nodejs/node-gyp/commit/c4729129daa9bb5204246b857826fb391ac961e1))
* update supported vs versions ([#2959](https://github.com/nodejs/node-gyp/issues/2959)) ([391cc5b](https://github.com/nodejs/node-gyp/commit/391cc5b9b25cffe0cb2edcba3583414a771b4a15))
### Doc
* npm is currently v10 ([#2970](https://github.com/nodejs/node-gyp/issues/2970)) ([7705a22](https://github.com/nodejs/node-gyp/commit/7705a22f31a62076e9f8429780a459f4ad71ea4c))
* remove outdated Node versions from readme ([#2955](https://github.com/nodejs/node-gyp/issues/2955)) ([ae8478e](https://github.com/nodejs/node-gyp/commit/ae8478ec32d9b2fa71b591ac22cdf867ef2e9a7d))
* remove outdated update engines.node reference in 10.0.0 changelog ([b42e796](https://github.com/nodejs/node-gyp/commit/b42e7966177f006f3d1aab1d27885d8372c8ed01))
### Miscellaneous
* only run release please on push ([cff9ac2](https://github.com/nodejs/node-gyp/commit/cff9ac2c3083769a383e00bc60b91562f03116e3))
* upgrade release please action from v2 to v4 ([#2982](https://github.com/nodejs/node-gyp/issues/2982)) ([0035d8e](https://github.com/nodejs/node-gyp/commit/0035d8e9dc98b94f0bc8cd9023a6fa635003703e))
### [10.0.1](https://www.github.com/nodejs/node-gyp/compare/v10.0.0...v10.0.1) (2023-11-02)
### Bug Fixes
* use local `util` for `findAccessibleSync()` ([b39e681](https://www.github.com/nodejs/node-gyp/commit/b39e6819aa9e2c45107d6e60a4913ca036ebfbfd))
### Miscellaneous
* add parallel test logging ([7de1f5f](https://www.github.com/nodejs/node-gyp/commit/7de1f5f32d550d26d48fe4f76aed5866744edcba))
* lint fixes ([4e0ed99](https://www.github.com/nodejs/node-gyp/commit/4e0ed992566f43abc6e988af091ad07fde04acbf))
* use platform specific timeouts in tests ([a68586a](https://www.github.com/nodejs/node-gyp/commit/a68586a67d0af238300662cc062422b42820044d))
## [10.0.0](https://www.github.com/nodejs/node-gyp/compare/v9.4.0...v10.0.0) (2023-10-28)
### ⚠ BREAKING CHANGES
* use .npmignore file to limit which files are published (#2921)
* the `Gyp` class exported is now created using ECMAScript classes and therefore might have small differences to classes that were previously created with `util.inherits`.
* All internal functions have been coverted to return promises and no longer accept callbacks. This is not a breaking change for users but may be breaking to consumers of `node-gyp` if you are requiring internal functions directly.
* `node-gyp` now supports node `^16.14.0 || >=18.0.0`
### Features
* convert all internal functions to async/await ([355622f](https://www.github.com/nodejs/node-gyp/commit/355622f4aac3bd3056b9e03aac5fa2f42a4b3576))
* convert internal classes from util.inherits to classes ([d52997e](https://www.github.com/nodejs/node-gyp/commit/d52997e975b9da6e0cea3d9b99873e9ddc768679))
* drop node 14 support ([#2929](https://www.github.com/nodejs/node-gyp/issues/2929)) ([1b3bd34](https://www.github.com/nodejs/node-gyp/commit/1b3bd341b40f384988d03207ce8187e93ba609bc))
* drop rimraf dependency ([4a50fe3](https://www.github.com/nodejs/node-gyp/commit/4a50fe31574217c4b2a798fc72b19947a64ceea1))
* **gyp:** update gyp to v0.16.1 ([#2923](https://www.github.com/nodejs/node-gyp/issues/2923)) ([707927c](https://www.github.com/nodejs/node-gyp/commit/707927cd579205ef2b4b17e61c1cce24c056b452))
* replace npmlog with proc-log ([4a50fe3](https://www.github.com/nodejs/node-gyp/commit/4a50fe31574217c4b2a798fc72b19947a64ceea1))
* update engines.node to ^14.17.0 || ^16.13.0 || >=18.0.0 ([4a50fe3](https://www.github.com/nodejs/node-gyp/commit/4a50fe31574217c4b2a798fc72b19947a64ceea1))
* use .npmignore file to limit which files are published ([#2921](https://www.github.com/nodejs/node-gyp/issues/2921)) ([864a979](https://www.github.com/nodejs/node-gyp/commit/864a979930cf0ef5ad64bc887b901fa8955d058f))
### Bug Fixes
* create Python symlink only during builds, and clean it up after ([#2721](https://www.github.com/nodejs/node-gyp/issues/2721)) ([0f1f667](https://www.github.com/nodejs/node-gyp/commit/0f1f667b737d21905e283df100a2cb639993562a))
* promisify build command ([4a50fe3](https://www.github.com/nodejs/node-gyp/commit/4a50fe31574217c4b2a798fc72b19947a64ceea1))
* use fs/promises in favor of fs.promises ([4a50fe3](https://www.github.com/nodejs/node-gyp/commit/4a50fe31574217c4b2a798fc72b19947a64ceea1))
### Tests
* increase mocha timeout ([#2887](https://www.github.com/nodejs/node-gyp/issues/2887)) ([445c28f](https://www.github.com/nodejs/node-gyp/commit/445c28fabc5fbdf9c3bb3341fb70660a3530f6ad))
* update expired certs ([#2908](https://www.github.com/nodejs/node-gyp/issues/2908)) ([5746691](https://www.github.com/nodejs/node-gyp/commit/5746691a36f7b37019d4b8d4e9616aec43d20410))
### Doc
* Add note about Python symlinks (PR 2362) to CHANGELOG.md for 9.1.0 ([#2783](https://www.github.com/nodejs/node-gyp/issues/2783)) ([b3d41ae](https://www.github.com/nodejs/node-gyp/commit/b3d41aeb737ddd54cc292f363abc561dcc0a614e))
* README.md Do not hardcode the supported versions of Python ([#2880](https://www.github.com/nodejs/node-gyp/issues/2880)) ([bb93b94](https://www.github.com/nodejs/node-gyp/commit/bb93b946a9c74934b59164deb52128cf913c97d5))
* update applicable GitHub links from master to main ([#2843](https://www.github.com/nodejs/node-gyp/issues/2843)) ([d644ce4](https://www.github.com/nodejs/node-gyp/commit/d644ce48311edf090d0e920ad449e5766c757933))
* Update windows installation instructions in README.md ([#2882](https://www.github.com/nodejs/node-gyp/issues/2882)) ([c9caa2e](https://www.github.com/nodejs/node-gyp/commit/c9caa2ecf3c7deae68444ce8fabb32d2dca651cd))
### Core
* find python checks order changed on windows ([#2872](https://www.github.com/nodejs/node-gyp/issues/2872)) ([b030555](https://www.github.com/nodejs/node-gyp/commit/b030555cdb754d9c23906e7e707115cd077bbf76))
* glob@10.3.10 ([#2926](https://www.github.com/nodejs/node-gyp/issues/2926)) ([4bef1ec](https://www.github.com/nodejs/node-gyp/commit/4bef1ecc7554097d92beb397fbe1a546c5227545))
* glob@8.0.3 ([4a50fe3](https://www.github.com/nodejs/node-gyp/commit/4a50fe31574217c4b2a798fc72b19947a64ceea1))
* make-fetch-happen@13.0.0 ([#2927](https://www.github.com/nodejs/node-gyp/issues/2927)) ([059bb6f](https://www.github.com/nodejs/node-gyp/commit/059bb6fd41bb50955a9efbd97887773d60d53221))
* nopt@^7.0.0 ([4a50fe3](https://www.github.com/nodejs/node-gyp/commit/4a50fe31574217c4b2a798fc72b19947a64ceea1))
* standard@17.0.0 and fix linting errors ([4a50fe3](https://www.github.com/nodejs/node-gyp/commit/4a50fe31574217c4b2a798fc72b19947a64ceea1))
* which@3.0.0 ([4a50fe3](https://www.github.com/nodejs/node-gyp/commit/4a50fe31574217c4b2a798fc72b19947a64ceea1))
* which@4.0.0 ([#2928](https://www.github.com/nodejs/node-gyp/issues/2928)) ([e388255](https://www.github.com/nodejs/node-gyp/commit/e38825531403aabeae7abe58e76867f31b832f36))
### Miscellaneous
* add check engines script to CI ([#2922](https://www.github.com/nodejs/node-gyp/issues/2922)) ([21a7249](https://www.github.com/nodejs/node-gyp/commit/21a7249b40d8f95e7721e450fd18764adb1648a7))
* empty commit to add changelog entries from [#2770](https://www.github.com/nodejs/node-gyp/issues/2770) ([4a50fe3](https://www.github.com/nodejs/node-gyp/commit/4a50fe31574217c4b2a798fc72b19947a64ceea1))
* GitHub Workflows security hardening ([#2740](https://www.github.com/nodejs/node-gyp/issues/2740)) ([26683e9](https://www.github.com/nodejs/node-gyp/commit/26683e993df038fb94d89f2276f3535e4522d79a))
* misc testing fixes ([#2930](https://www.github.com/nodejs/node-gyp/issues/2930)) ([4e493d4](https://www.github.com/nodejs/node-gyp/commit/4e493d4fb262d12ac52c84979071ccc79e666a1a))
* run tests after release please PR ([3032e10](https://www.github.com/nodejs/node-gyp/commit/3032e1061cc2b7b49f83c397d385bafddc6b0214))
## [9.4.0](https://www.github.com/nodejs/node-gyp/compare/v9.3.1...v9.4.0) (2023-06-12)
@ -117,6 +511,7 @@
### Core
* update due to rename of primary branch ([ca1f068](https://www.github.com/nodejs/node-gyp/commit/ca1f0681a5567ca8cd51acebccd37a633f19bc6a))
* Add Python symlink to path (for non-Windows OSes only) ([#2362](https://github.com/nodejs/node-gyp/pull/2362)) ([b9ddcd5](https://github.com/nodejs/node-gyp/commit/b9ddcd5bbd93b05b03674836b6ebdae2c2e74c8c))
### Tests
@ -646,11 +1041,11 @@ Republish of v5.0.6 with unnecessary tarball removed from pack file.
* [[`94c39c604e`](https://github.com/nodejs/node-gyp/commit/94c39c604e)] - **gyp**: fix ninja build failure (GYP patch) (Daniel Bevenius) [nodejs/node#12484](https://github.com/nodejs/node/pull/12484)
* [[`e8ea74e0fa`](https://github.com/nodejs/node-gyp/commit/e8ea74e0fa)] - **tools**: patch gyp to avoid xcrun errors (Ujjwal Sharma) [nodejs/node#21520](https://github.com/nodejs/node/pull/21520)
* [[`ea9aff44f2`](https://github.com/nodejs/node-gyp/commit/ea9aff44f2)] - **tools**: fix "the the" typos in comments (Masashi Hirano) [nodejs/node#20716](https://github.com/nodejs/node/pull/20716)
* [[`207e5aa4fd`](https://github.com/nodejs/node-gyp/commit/207e5aa4fd)] - **gyp**: implement LD/LDXX for ninja and FIPS (Sam Roberts)
* [[`207e5aa4fd`](https://github.com/nodejs/node-gyp/commit/207e5aa4fd)] - **gyp**: implement LD/LDXX for ninja and FIPS (Sam Roberts)
* [[`b416c5f4b7`](https://github.com/nodejs/node-gyp/commit/b416c5f4b7)] - **gyp**: enable cctest to use objects (gyp part) (Daniel Bevenius) [nodejs/node#12450](https://github.com/nodejs/node/pull/12450)
* [[`40692d016b`](https://github.com/nodejs/node-gyp/commit/40692d016b)] - **gyp**: add compile\_commands.json gyp generator (Ben Noordhuis) [nodejs/node#12450](https://github.com/nodejs/node/pull/12450)
* [[`fc3c4e2b10`](https://github.com/nodejs/node-gyp/commit/fc3c4e2b10)] - **gyp**: float gyp patch for long filenames (Anna Henningsen) [nodejs/node#7963](https://github.com/nodejs/node/pull/7963)
* [[`8aedbfdef6`](https://github.com/nodejs/node-gyp/commit/8aedbfdef6)] - **gyp**: backport GYP fix to fix AIX shared suffix (Stewart Addison)
* [[`8aedbfdef6`](https://github.com/nodejs/node-gyp/commit/8aedbfdef6)] - **gyp**: backport GYP fix to fix AIX shared suffix (Stewart Addison)
* [[`6cd84b84fc`](https://github.com/nodejs/node-gyp/commit/6cd84b84fc)] - **test**: formatting and minor fixes for execFileSync replacement (Rod Vagg) [#1521](https://github.com/nodejs/node-gyp/pull/1521)
* [[`60e421363f`](https://github.com/nodejs/node-gyp/commit/60e421363f)] - **test**: added test/processExecSync.js for when execFileSync is not available. (Rohit Hazra) [#1492](https://github.com/nodejs/node-gyp/pull/1492)
* [[`969447c5bd`](https://github.com/nodejs/node-gyp/commit/969447c5bd)] - **deps**: bump request to 2.8.7, fixes heok/hawk issues (Rohit Hazra) [#1492](https://github.com/nodejs/node-gyp/pull/1492)
@ -712,7 +1107,7 @@ Republish of v5.0.6 with unnecessary tarball removed from pack file.
## v3.5.0 2017-01-10
* [[`762d19a39e`](https://github.com/nodejs/node-gyp/commit/762d19a39e)] - \[doc\] merge History.md and CHANGELOG.md (Rod Vagg)
* [[`762d19a39e`](https://github.com/nodejs/node-gyp/commit/762d19a39e)] - \[doc\] merge History.md and CHANGELOG.md (Rod Vagg)
* [[`80fc5c3d31`](https://github.com/nodejs/node-gyp/commit/80fc5c3d31)] - Fix deprecated dependency warning (Simone Primarosa) [#1069](https://github.com/nodejs/node-gyp/pull/1069)
* [[`05c44944fd`](https://github.com/nodejs/node-gyp/commit/05c44944fd)] - Open the build file with universal-newlines mode (Guy Margalit) [#1053](https://github.com/nodejs/node-gyp/pull/1053)
* [[`37ae7be114`](https://github.com/nodejs/node-gyp/commit/37ae7be114)] - Try python launcher when stock python is python 3. (Ben Noordhuis) [#992](https://github.com/nodejs/node-gyp/pull/992)
@ -769,7 +1164,7 @@ Republish of v5.0.6 with unnecessary tarball removed from pack file.
* [[`0e2dfda1f3`](https://github.com/nodejs/node-gyp/commit/0e2dfda1f3)] - Fix test/test-options when run through `npm test`. (Ben Noordhuis) [#755](https://github.com/nodejs/node-gyp/pull/755)
* [[`9bfa0876b4`](https://github.com/nodejs/node-gyp/commit/9bfa0876b4)] - Add support for AIX (Michael Dawson) [#753](https://github.com/nodejs/node-gyp/pull/753)
* [[`a8d441a0a2`](https://github.com/nodejs/node-gyp/commit/a8d441a0a2)] - Update README for Windows 10 support. (Jason Williams) [#766](https://github.com/nodejs/node-gyp/pull/766)
* [[`d1d6015276`](https://github.com/nodejs/node-gyp/commit/d1d6015276)] - Update broken links and switch to HTTPS. (andrew morton)
* [[`d1d6015276`](https://github.com/nodejs/node-gyp/commit/d1d6015276)] - Update broken links and switch to HTTPS. (andrew morton)
## v3.1.0 2015-11-14

View file

@ -1,4 +1,4 @@
# Code of Conduct
* [Node.js Code of Conduct](https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md)
* [Node.js Moderation Policy](https://github.com/nodejs/admin/blob/HEAD/Moderation-Policy.md)
* [Node.js Code of Conduct](https://github.com/nodejs/admin/blob/master/CODE_OF_CONDUCT.md)
* [Node.js Moderation Policy](https://github.com/nodejs/admin/blob/master/Moderation-Policy.md)

View file

@ -1,9 +1,17 @@
# Contributing to node-gyp
## Making changes to gyp-next
Changes in the subfolder `gyp/` should be submitted to the
[`gyp-next`][] repository first. The `gyp/` folder is regularly
synced from [`gyp-next`][] with GitHub Actions workflow
[`update-gyp-next.yml`](.github/workflows/update-gyp-next.yml),
and any changes in this folder would be overridden by the workflow.
## Code of Conduct
Please read the
[Code of Conduct](https://github.com/nodejs/admin/blob/master/CODE_OF_CONDUCT.md)
[Code of Conduct](https://github.com/nodejs/admin/blob/main/CODE_OF_CONDUCT.md)
which explains the minimum behavior expectations for node-gyp contributors.
<a id="developers-certificate-of-origin"></a>
@ -32,3 +40,5 @@ By making a contribution to this project, I certify that:
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
[`gyp-next`]: https://github.com/nodejs/gyp-next

View file

@ -1,18 +1,18 @@
# `node-gyp` - Node.js native addon build tool
[![Build Status](https://github.com/nodejs/node-gyp/workflows/Tests/badge.svg?branch=master)](https://github.com/nodejs/node-gyp/actions?query=workflow%3ATests+branch%3Amaster)
[![Build Status](https://github.com/nodejs/node-gyp/workflows/Tests/badge.svg?branch=main)](https://github.com/nodejs/node-gyp/actions?query=workflow%3ATests+branch%3Amain)
![npm](https://img.shields.io/npm/dm/node-gyp)
`node-gyp` is a cross-platform command-line tool written in Node.js for
compiling native addon modules for Node.js. It contains a vendored copy of the
[gyp-next](https://github.com/nodejs/gyp-next) project that was previously used
by the Chromium team, extended to support the development of Node.js native addons.
by the Chromium team and extended to support the development of Node.js native
addons.
Note that `node-gyp` is _not_ used to build Node.js itself.
Multiple target versions of Node.js are supported (i.e. `0.8`, ..., `4`, `5`, `6`,
etc.), regardless of what version of Node.js is actually installed on your system
(`node-gyp` downloads the necessary development files or headers for the target version).
All current and LTS target versions of Node.js are supported. Depending on what version of Node.js is actually installed on your system
`node-gyp` downloads the necessary development files or headers for the target version. List of stable Node.js versions can be found on [Node.js website](https://nodejs.org/en/about/previous-releases).
## Features
@ -21,6 +21,9 @@ etc.), regardless of what version of Node.js is actually installed on your syste
## Installation
> [!Important]
> Python >= v3.12 requires `node-gyp` >= v10
You can install `node-gyp` using `npm`:
``` bash
@ -31,40 +34,44 @@ Depending on your operating system, you will need to install:
### On Unix
* Python v3.7, v3.8, v3.9, or v3.10
* [A supported version of Python](https://devguide.python.org/versions/)
* `make`
* A proper C/C++ compiler toolchain, like [GCC](https://gcc.gnu.org)
### On macOS
**ATTENTION**: If your Mac has been _upgraded_ to macOS Catalina (10.15) or higher, please read [macOS_Catalina.md](macOS_Catalina.md).
* Python v3.7, v3.8, v3.9, or v3.10
* `XCode Command Line Tools` which will install `clang`, `clang++`, and `make`.
* Install the `XCode Command Line Tools` standalone by running `xcode-select --install`. -- OR --
* [A supported version of Python](https://devguide.python.org/versions/)
* `Xcode Command Line Tools` which will install `clang`, `clang++`, and `make`.
* Install the `Xcode Command Line Tools` standalone by running `xcode-select --install`. -- OR --
* Alternatively, if you already have the [full Xcode installed](https://developer.apple.com/xcode/download/), you can install the Command Line Tools under the menu `Xcode -> Open Developer Tool -> More Developer Tools...`.
### On Windows
Install the current version of Python from the [Microsoft Store package](https://www.microsoft.com/en-us/p/python-310/9pjpw5ldxlz5).
Install tools with [Chocolatey](https://chocolatey.org):
``` bash
choco install python visualstudio2022-workload-vctools -y
```
Install tools and configuration manually:
* Install Visual C++ Build Environment: [Visual Studio Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools)
(using "Visual C++ build tools" workload) or [Visual Studio Community](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community)
(using the "Desktop development with C++" workload)
Or install and configure Python and Visual Studio tools manually:
If the above steps didn't work for you, please visit [Microsoft's Node.js Guidelines for Windows](https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md#compiling-native-addon-modules) for additional tips.
* Follow the instructions in [Using Python on Windows](https://docs.python.org/3/using/windows.html) to install
the current [version of Python](https://www.python.org/downloads/).
* Install Visual C++ Build Environment: For Visual Studio 2019 or later, use the `Desktop development with C++` workload from [Visual Studio Community](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community). For a version older than Visual Studio 2019, install [Visual Studio Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools) with the `Visual C++ build tools` option.
To target native ARM64 Node.js on Windows on ARM, add the components "Visual C++ compilers and libraries for ARM64" and "Visual C++ ATL for ARM64".
To use the native ARM64 C++ compiler on Windows on ARM, ensure that you have Visual Studio 2022 [17.4 or later](https://devblogs.microsoft.com/visualstudio/arm64-visual-studio-is-officially-here/) installed.
It's advised to install the following PowerShell module: [VSSetup](https://github.com/microsoft/vssetup.powershell) using `Install-Module VSSetup -Scope CurrentUser`.
This will make Visual Studio detection logic use a more flexible and accessible method, avoiding PowerShell's `ConstrainedLanguage` mode.
### Configuring Python Dependency
`node-gyp` requires that you have installed a compatible version of Python, one of: v3.7, v3.8,
v3.9, or v3.10. If you have multiple Python versions installed, you can identify which Python
version `node-gyp` should use in one of the following ways:
`node-gyp` requires that you have installed a [supported version of Python](https://devguide.python.org/versions/).
If you have multiple versions of Python installed, you can identify which version
`node-gyp` should use in one of the following ways:
1. by setting the `--python` command-line option, e.g.:
@ -73,24 +80,29 @@ node-gyp <command> --python /path/to/executable/python
```
2. If `node-gyp` is called by way of `npm`, *and* you have multiple versions of
Python installed, then you can set `npm`'s 'python' config key to the appropriate
value:
Python installed, then you can set the `npm_config_python` environment variable
to the appropriate path:
``` bash
npm config set python /path/to/executable/python
export npm_config_python=/path/to/executable/python
```
&nbsp;&nbsp;&nbsp;&nbsp;Or on Windows:
```console
py --list-paths # To see the installed Python versions
set npm_config_python=C:\path\to\python.exe # CMD
$Env:npm_config_python="C:\path\to\python.exe" # PowerShell
```
3. If the `PYTHON` environment variable is set to the path of a Python executable,
then that version will be used, if it is a compatible version.
then that version will be used if it is a supported version.
4. If the `NODE_GYP_FORCE_PYTHON` environment variable is set to the path of a
Python executable, it will be used instead of any of the other configured or
builtin Python search paths. If it's not a compatible version, no further
built-in Python search paths. If it's not a compatible version, no further
searching will be done.
### Build for Third Party Node.js Runtimes
When building modules for third party Node.js runtimes like Electron, which have
When building modules for third-party Node.js runtimes like Electron, which have
different build configurations from the official Node.js distribution, you
should use `--dist-url` or `--nodedir` flags to specify the headers of the
runtime to build for.
@ -106,7 +118,7 @@ to work around configuration errors.
## How to Use
To compile your native addon, first go to its root directory:
To compile your native addon first go to its root directory:
``` bash
cd my_node_addon
@ -168,7 +180,7 @@ The **[docs](./docs/)** directory contains additional documentation on specific
Some additional resources for Node.js native addons and writing `gyp` configuration files:
* ["Going Native" a nodeschool.io tutorial](http://nodeschool.io/#goingnative)
* ["Hello World" node addon example](https://github.com/nodejs/node/tree/master/test/addons/hello-world)
* ["Hello World" node addon example](https://github.com/nodejs/node/tree/main/test/addons/hello-world)
* [gyp user documentation](https://gyp.gsrc.io/docs/UserDocumentation.md)
* [gyp input format reference](https://gyp.gsrc.io/docs/InputFormatReference.md)
* [*"binding.gyp" files out in the wild* wiki page](./docs/binding.gyp-files-in-the-wild.md)
@ -221,9 +233,24 @@ Some additional resources for Node.js native addons and writing `gyp` configurat
## Configuration
### package.json
Use the `config` object in your package.json with each key in the form `node_gyp_OPTION_NAME`. Any of the command
options listed above can be set (dashes in option names should be replaced by underscores).
For example, to set `devdir` equal to `/tmp/.gyp`, your package.json would contain this:
```json
{
"config": {
"node_gyp_devdir": "/tmp/.gyp"
}
}
```
### Environment variables
Use the form `npm_config_OPTION_NAME` for any of the command options listed
Use the form `npm_package_config_node_gyp_OPTION_NAME` for any of the command options listed
above (dashes in option names should be replaced by underscores).
For example, to set `devdir` equal to `/tmp/.gyp`, you would:
@ -231,16 +258,20 @@ For example, to set `devdir` equal to `/tmp/.gyp`, you would:
Run this on Unix:
```bash
export npm_config_devdir=/tmp/.gyp
export npm_package_config_node_gyp_devdir=/tmp/.gyp
```
Or this on Windows:
```console
set npm_config_devdir=c:\temp\.gyp
set npm_package_config_node_gyp_devdir=c:\temp\.gyp
```
### `npm` configuration
Note that in versions of npm before v11 it was possible to use the prefix `npm_config_` for
environment variables. This was deprecated in npm@11 and will be removed in npm@12 so it
is recommended to convert your environment variables to the above format.
### `npm` configuration for npm versions before v9
Use the form `OPTION_NAME` for any of the command options listed above.

View file

@ -179,7 +179,7 @@
'-loleaut32.lib',
'-luuid.lib',
'-lodbc32.lib',
'-lDelayImp.lib',
'-ldelayimp.lib',
'-l"<(node_lib_file)"'
],
'msvs_disabled_warnings': [
@ -195,7 +195,7 @@
'_FILE_OFFSET_BITS=64'
],
}],
[ 'OS in "freebsd openbsd netbsd solaris android" or \
[ 'OS in "freebsd openbsd netbsd solaris android openharmony" or \
(OS=="linux" and target_arch!="ia32")', {
'cflags': [ '-fPIC' ],
}],

View file

@ -6,7 +6,7 @@ process.title = 'node-gyp'
const envPaths = require('env-paths')
const gyp = require('../')
const log = require('npmlog')
const log = require('../lib/log')
const os = require('os')
/**
@ -14,11 +14,11 @@ const os = require('os')
*/
const prog = gyp()
var completed = false
let completed = false
prog.parseArgv(process.argv)
prog.devDir = prog.opts.devdir
var homeDir = os.homedir()
const homeDir = os.homedir()
if (prog.devDir) {
prog.devDir = prog.devDir.replace(/^~/, homeDir)
} else if (homeDir) {
@ -32,9 +32,9 @@ if (prog.devDir) {
if (prog.todo.length === 0) {
if (~process.argv.indexOf('-v') || ~process.argv.indexOf('--version')) {
console.log('v%s', prog.version)
log.stdout('v%s', prog.version)
} else {
console.log('%s', prog.usage())
log.stdout('%s', prog.usage())
}
process.exit(0)
}
@ -48,11 +48,11 @@ log.info('using', 'node@%s | %s | %s', process.versions.node, process.platform,
* Change dir if -C/--directory was passed.
*/
var dir = prog.opts.directory
const dir = prog.opts.directory
if (dir) {
var fs = require('fs')
const fs = require('fs')
try {
var stat = fs.statSync(dir)
const stat = fs.statSync(dir)
if (stat.isDirectory()) {
log.info('chdir', dir)
process.chdir(dir)
@ -68,8 +68,8 @@ if (dir) {
}
}
function run () {
var command = prog.todo.shift()
async function run () {
const command = prog.todo.shift()
if (!command) {
// done!
completed = true
@ -77,30 +77,28 @@ function run () {
return
}
prog.commands[command.name](command.args, function (err) {
if (err) {
log.error(command.name + ' error')
log.error('stack', err.stack)
errorMessage()
log.error('not ok')
return process.exit(1)
}
try {
const args = await prog.commands[command.name](command.args) ?? []
if (command.name === 'list') {
var versions = arguments[1]
if (versions.length > 0) {
versions.forEach(function (version) {
console.log(version)
})
if (args.length) {
args.forEach((version) => log.stdout(version))
} else {
console.log('No node development files installed. Use `node-gyp install` to install a version.')
log.stdout('No node development files installed. Use `node-gyp install` to install a version.')
}
} else if (arguments.length >= 2) {
console.log.apply(console, [].slice.call(arguments, 1))
} else if (args.length >= 1) {
log.stdout(...args.slice(1))
}
// now run the next command in the queue
process.nextTick(run)
})
return run()
} catch (err) {
log.error(command.name + ' error')
log.error('stack', err.stack)
errorMessage()
log.error('not ok')
return process.exit(1)
}
}
process.on('exit', function (code) {
@ -120,13 +118,20 @@ process.on('uncaughtException', function (err) {
function errorMessage () {
// copied from npm's lib/utils/error-handler.js
var os = require('os')
const os = require('os')
log.error('System', os.type() + ' ' + os.release())
log.error('command', process.argv
.map(JSON.stringify).join(' '))
log.error('cwd', process.cwd())
log.error('node -v', process.version)
log.error('node-gyp -v', 'v' + prog.package.version)
// print the npm package version
for (const env of ['npm_package_name', 'npm_package_version']) {
const value = process.env[env]
if (value != null) {
log.error(`$${env}`, value)
}
}
}
function issueMessage () {

View file

@ -1,94 +0,0 @@
When using `node-gyp` you might see an error like this when attempting to compile/install a node.js native addon:
```
$ npm install bcrypt
npm http GET https://registry.npmjs.org/bcrypt/0.7.5
npm http 304 https://registry.npmjs.org/bcrypt/0.7.5
npm http GET https://registry.npmjs.org/bindings/1.0.0
npm http 304 https://registry.npmjs.org/bindings/1.0.0
> bcrypt@0.7.5 install /home/ubuntu/public/song-swap/node_modules/bcrypt
> node-gyp rebuild
gyp ERR! configure error
gyp ERR! stack Error: "pre" versions of node cannot be installed, use the --nodedir flag instead
gyp ERR! stack at install (/usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/install.js:69:16)
gyp ERR! stack at Object.self.commands.(anonymous function) [as install] (/usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/node-gyp.js:56:37)
gyp ERR! stack at getNodeDir (/usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:219:20)
gyp ERR! stack at /usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:105:9
gyp ERR! stack at ChildProcess.exithandler (child_process.js:630:7)
gyp ERR! stack at ChildProcess.EventEmitter.emit (events.js:99:17)
gyp ERR! stack at maybeClose (child_process.js:730:16)
gyp ERR! stack at Process.ChildProcess._handle.onexit (child_process.js:797:5)
gyp ERR! System Linux 3.5.0-21-generic
gyp ERR! command "node" "/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /home/ubuntu/public/song-swap/node_modules/bcrypt
gyp ERR! node -v v0.11.2-pre
gyp ERR! node-gyp -v v0.9.5
gyp ERR! not ok
npm ERR! bcrypt@0.7.5 install: `node-gyp rebuild`
npm ERR! `sh "-c" "node-gyp rebuild"` failed with 1
npm ERR!
npm ERR! Failed at the bcrypt@0.7.5 install script.
npm ERR! This is most likely a problem with the bcrypt package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR! node-gyp rebuild
npm ERR! You can get their info via:
npm ERR! npm owner ls bcrypt
npm ERR! There is likely additional logging output above.
npm ERR! System Linux 3.5.0-21-generic
npm ERR! command "/usr/local/bin/node" "/usr/local/bin/npm" "install" "bcrypt"
npm ERR! cwd /home/ubuntu/public/song-swap
npm ERR! node -v v0.11.2-pre
npm ERR! npm -v 1.2.18
npm ERR! code ELIFECYCLE
npm ERR!
npm ERR! Additional logging details can be found in:
npm ERR! /home/ubuntu/public/song-swap/npm-debug.log
npm ERR! not ok code 0
```
The main error here is:
```
Error: "pre" versions of node cannot be installed, use the --nodedir flag instead
```
This error is caused when you attempt to compile a native addon using a version of node.js with `-pre` at the end of the version number:
``` bash
$ node -v
v0.10.4-pre
```
## How to avoid (the short answer)
To avoid this error completely just use a stable release of node.js. i.e. `v0.10.4`, and __not__ `v0.10.4-pre`.
## How to fix (the long answer)
This error happens because `node-gyp` does not know what header files were used to compile your "pre" version of node, and therefore it needs you to specify the node source code directory path using the `--nodedir` flag.
For example, if I compiled my development ("pre") version of node.js using the source code in `/Users/nrajlich/node`, then I could invoke `node-gyp` like:
``` bash
$ node-gyp rebuild --nodedir=/Users/nrajlich/node
```
Or install an native addon through `npm` like:
``` bash
$ npm install bcrypt --nodedir=/Users/nrajlich/node
```
### Always use `--nodedir`
__Note:__ This is for advanced users who use `-pre` versions of node more often than tagged releases.
If you're invoking `node-gyp` through `npm`, then you can leverage `npm`'s configuration system and not have to specify the `--nodedir` flag all the time:
``` bash
$ npm config set nodedir /Users/nrajlich/node
```

View file

@ -1,47 +0,0 @@
# Force npm to use global installed node-gyp
**Note: These instructions only work with npm 6 or older. For a solution that works with npm 8 (or older), see [Updating-npm-bundled-node-gyp.md](Updating-npm-bundled-node-gyp.md).**
[Many issues](https://github.com/nodejs/node-gyp/labels/ERR%21%20node-gyp%20-v%20%3C%3D%20v5.1.0) are opened by users who are
not running a [current version of node-gyp](https://github.com/nodejs/node-gyp/releases).
npm bundles its own, internal, copy of node-gyp located at `npm/node_modules`, within npm's private dependencies which are separate from *globally* accessible packages. Therefore this internal copy of node-gyp is independent from any globally installed copy of node-gyp that
may have been installed via `npm install -g node-gyp`.
So npm's internal copy of node-gyp **isn't** stored inside *global* `node_modules` and thus isn't available for use as a standalone package. npm uses it's *internal* copy of `node-gyp` to automatically build native addons.
When you install a _new_ version of node-gyp outside of npm, it'll go into your *global* `node_modules`, but not under the `npm/node_modules` (where internal copy of node-gyp is stored). So it will get into your `$PATH` and you will be able to use this globally installed version (**but not internal node-gyp of npm**) as any other globally installed package.
The catch is that npm **won't** use global version unless you tell it to, it'll keep on using the **internal one**. You need to instruct it to by setting the `node_gyp` config variable (which goes into your `~/.npmrc`). You do this by running the `npm config set` command as below. Then npm will use the command in the path you supply whenever it needs to build a native addon.
**Important**: You also need to remember to unset this when you upgrade npm with a newer version of node-gyp, or you have to manually keep your globally installed node-gyp to date. See "Undo" below.
## Linux and macOS
```
npm install --global node-gyp@latest
npm config set node_gyp $(npm prefix -g)/lib/node_modules/node-gyp/bin/node-gyp.js
```
`sudo` may be required for the first command if you get a permission error.
## Windows
### Windows Command Prompt
```
npm install --global node-gyp@latest
for /f "delims=" %P in ('npm prefix -g') do npm config set node_gyp "%P\node_modules\node-gyp\bin\node-gyp.js"
```
### Powershell
```
npm install --global node-gyp@latest
npm prefix -g | % {npm config set node_gyp "$_\node_modules\node-gyp\bin\node-gyp.js"}
```
## Undo
**Beware** if you don't unset the `node_gyp` config option, npm will continue to use the globally installed version of node-gyp rather than the one it ships with, which may end up being newer.
```
npm config delete node_gyp
npm uninstall --global node-gyp
```

View file

@ -1,7 +0,0 @@
Welcome to the node-gyp wiki!
* [["binding.gyp" files out in the wild]]
* [[Linking to OpenSSL]]
* [[Common Issues]]
* [[Updating npm's bundled node-gyp]]
* [[Error: "pre" versions of node cannot be installed]]

View file

@ -1,86 +0,0 @@
A handful of native addons require linking to OpenSSL in one way or another. This introduces a small challenge since node will sometimes bundle OpenSSL statically (the default for node >= v0.8.x), or sometimes dynamically link to the system OpenSSL (default for node <= v0.6.x).
Good native addons should account for both scenarios. It's recommended that you use the `binding.gyp` file provided below as a starting-point for any addon that needs to use OpenSSL:
``` python
{
'variables': {
# node v0.6.x doesn't give us its build variables,
# but on Unix it was only possible to use the system OpenSSL library,
# so default the variable to "true", v0.8.x node and up will overwrite it.
'node_shared_openssl%': 'true'
},
'targets': [
{
'target_name': 'binding',
'sources': [
'src/binding.cc'
],
'conditions': [
['node_shared_openssl=="false"', {
# so when "node_shared_openssl" is "false", then OpenSSL has been
# bundled into the node executable. So we need to include the same
# header files that were used when building node.
'include_dirs': [
'<(node_root_dir)/deps/openssl/openssl/include'
],
"conditions" : [
["target_arch=='ia32'", {
"include_dirs": [ "<(node_root_dir)/deps/openssl/config/piii" ]
}],
["target_arch=='x64'", {
"include_dirs": [ "<(node_root_dir)/deps/openssl/config/k8" ]
}],
["target_arch=='arm'", {
"include_dirs": [ "<(node_root_dir)/deps/openssl/config/arm" ]
}]
]
}]
]
}
]
}
```
This ensures that when OpenSSL is statically linked into `node` then, the bundled OpenSSL headers are included, but when the system OpenSSL is in use, then only those headers will be used.
## Windows?
As you can see this baseline `binding.gyp` file only accounts for the Unix scenario. Currently on Windows the situation is a little less ideal. On Windows, OpenSSL is _always_ statically compiled into the `node` executable, so ideally it would be possible to use that copy of OpenSSL when building native addons.
Unfortunately it doesn't seem like that is possible at the moment, as there would need to be tweaks made to the generated `node.lib` file to include the openssl glue functions, or a new `openssl.lib` file would need to be created during the node build. I'm not sure which is the easiest/most feasible.
In the meantime, one possible solution is using another copy of OpenSSL, which is what [`node-bcrypt`](https://github.com/ncb000gt/node.bcrypt.js) currently does. Adding something like this to your `binding.gyp` file's `"conditions"` block would enable this:
``` python
[ 'OS=="win"', {
'conditions': [
# "openssl_root" is the directory on Windows of the OpenSSL files.
# Check the "target_arch" variable to set good default values for
# both 64-bit and 32-bit builds of the module.
['target_arch=="x64"', {
'variables': {
'openssl_root%': 'C:/OpenSSL-Win64'
},
}, {
'variables': {
'openssl_root%': 'C:/OpenSSL-Win32'
},
}],
],
'libraries': [
'-l<(openssl_root)/lib/libeay32.lib',
],
'include_dirs': [
'<(openssl_root)/include',
],
}]
```
Now you can direct your users to install OpenSSL on Windows from here (be sure to tell them to install the 64-bit version if they're compiling against a 64-bit version of node): http://slproweb.com/products/Win32OpenSSL.html
Also note that both `node-gyp` and `npm` allow you to overwrite that default `openssl_root` variable on the command line:
``` bash
$ node-gyp rebuild --openssl-root="C:\Users\Nathan\Desktop\openssl"
```

View file

@ -1,19 +0,0 @@
## Versions of `node-gyp` that are earlier than v9.x.x
Please look thru your error log for the string `gyp info using node-gyp@` and if that version number is less than the [current release of node-gyp](https://github.com/nodejs/node-gyp/releases) then __please upgrade__ using [these instructions](https://github.com/nodejs/node-gyp/blob/master/docs/Updating-npm-bundled-node-gyp.md) and then try your command again.
## `node-sass` is deprecated
Please be aware that the package [`node-sass` is deprecated](https://github.com/sass/node-sass#node-sass) so you should actively seek alternatives. You can try:
```
npm uninstall node-sass
npm install sass --save
# or ...
npm install --global node-sass@latest
```
`node-sass` projects _may_ work by downgrading to Node.js v14 but [that release is end-of-life](https://github.com/nodejs/release#release-schedule).
But in any case, please avoid opening new `node-sass` issues on this repo because we [cannot help much](https://github.com/nodejs/node-gyp/issues?q=is%3Aissue+label%3A%22Node+Sass+--%3E+Dart+Sass%22+).
## Issues finding the installed Visual Studio
In cmd, [`npm config set msvs_version 20xx`](https://github.com/nodejs/node-gyp#on-windows) with ___xx___ matching your locally installed version of Visual Studio.

View file

@ -1,72 +0,0 @@
# Updating the npm-bundled version of node-gyp
**Note: These instructions are (only) tested and known to work with npm 8 and older.**
**Note: These instructions will be undone if you reinstall or upgrade npm or node! For a more permanent (and simpler) solution, see [Force-npm-to-use-global-node-gyp.md](Force-npm-to-use-global-node-gyp.md). (npm 6 or older only!)**
[Many issues](https://github.com/nodejs/node-gyp/issues?q=label%3A"ERR!+node-gyp+-v+<%3D+v9.x.x") are opened by users who are
not running a [current version of node-gyp](https://github.com/nodejs/node-gyp/releases).
`npm` bundles its own, internal, copy of `node-gyp`. This internal copy is independent of any globally installed copy of node-gyp that
may have been installed via `npm install -g node-gyp`.
This means that while `node-gyp` doesn't get installed into your `$PATH` by default, npm still keeps its own copy to invoke when you
attempt to `npm install` a native add-on.
Sometimes, you may need to update npm's internal node-gyp to a newer version than what is installed. A simple `npm install -g node-gyp`
_won't_ do the trick since npm will still continue to use its internal copy over the global one.
So instead:
## Version of npm
We need to start by knowing your version of `npm`:
```bash
npm --version
```
## Linux, macOS, Solaris, etc.
Unix is easy. Just run the following command.
If your npm is version ___7 or 8___, do:
```bash
$ npm explore npm/node_modules/@npmcli/run-script -g -- npm_config_global=false npm install node-gyp@latest
```
Else if your npm is version ___less than 7___, do:
```bash
$ npm explore npm/node_modules/npm-lifecycle -g -- npm install node-gyp@latest
```
If the command fails with a permissions error, please try `sudo` and then the command.
## Windows
Windows is a bit trickier, since `npm` might be installed to the "Program Files" directory, which needs admin privileges in order to
modify on current Windows. Therefore, run the following commands __inside a `cmd.exe` started with "Run as Administrator"__:
First we need to find the location of `node`. If you don't already know the location that `node.exe` got installed to, then run:
```bash
$ where node
```
Now `cd` to the directory that `node.exe` is contained in e.g.:
```bash
$ cd "C:\Program Files\nodejs"
```
If your npm version is ___7 or 8___, do:
```bash
cd node_modules\npm\node_modules\@npmcli\run-script
```
Else if your npm version is ___less than 7___, do:
```bash
cd node_modules\npm\node_modules\npm-lifecycle
```
Finish by running:
```bash
$ npm install node-gyp@latest
```

View file

@ -1,49 +0,0 @@
This page contains links to some examples of existing `binding.gyp` files that other node modules are using. Take a look at them for inspiration.
To add to this page, just add the link to the project's `binding.gyp` file below:
* [ons](https://github.com/XadillaX/aliyun-ons/blob/master/binding.gyp)
* [thmclrx](https://github.com/XadillaX/thmclrx/blob/master/binding.gyp)
* [libxmljs](https://github.com/polotek/libxmljs/blob/master/binding.gyp)
* [node-buffertools](https://github.com/bnoordhuis/node-buffertools/blob/master/binding.gyp)
* [node-canvas](https://github.com/LearnBoost/node-canvas/blob/master/binding.gyp)
* [node-ffi](https://github.com/rbranson/node-ffi/blob/master/binding.gyp) + [libffi](https://github.com/rbranson/node-ffi/blob/master/deps/libffi/libffi.gyp)
* [node-time](https://github.com/TooTallNate/node-time/blob/master/binding.gyp)
* [node-sass](https://github.com/sass/node-sass/blob/master/binding.gyp) + [libsass](https://github.com/sass/node-sass/blob/master/src/libsass.gyp)
* [node-serialport](https://github.com/voodootikigod/node-serialport/blob/master/binding.gyp)
* [node-weak](https://github.com/TooTallNate/node-weak/blob/master/binding.gyp)
* [pty.js](https://github.com/chjj/pty.js/blob/master/binding.gyp)
* [ref](https://github.com/TooTallNate/ref/blob/master/binding.gyp)
* [appjs](https://github.com/milani/appjs/blob/master/binding.gyp)
* [nwm](https://github.com/mixu/nwm/blob/master/binding.gyp)
* [bcrypt](https://github.com/ncb000gt/node.bcrypt.js/blob/master/binding.gyp)
* [nk-mysql](https://github.com/mmod/nodamysql/blob/master/binding.gyp)
* [nk-xrm-installer](https://github.com/mmod/nk-xrm-installer/blob/master/binding.gyp) + [includable.gypi](https://github.com/mmod/nk-xrm-installer/blob/master/includable.gypi) + [unpack.py](https://github.com/mmod/nk-xrm-installer/blob/master/unpack.py) + [disburse.py](https://github.com/mmod/nk-xrm-installer/blob/master/disburse.py)
<sub>.py files above provide complete reference for examples of fetching source via http, extracting, and moving files.</sub>
* [node-memwatch](https://github.com/lloyd/node-memwatch/blob/master/binding.gyp)
* [node-ip2location](https://github.com/bolgovr/node-ip2location/blob/master/binding.gyp)
* [node-midi](https://github.com/justinlatimer/node-midi/blob/master/binding.gyp)
* [node-sqlite3](https://github.com/developmentseed/node-sqlite3/blob/master/binding.gyp) + [libsqlite3](https://github.com/developmentseed/node-sqlite3/blob/master/deps/sqlite3.gyp)
* [node-zipfile](https://github.com/mapbox/node-zipfile/blob/master/binding.gyp)
* [node-mapnik](https://github.com/mapnik/node-mapnik/blob/master/binding.gyp)
* [node-inotify](https://github.com/c4milo/node-inotify/blob/master/binding.gyp)
* [v8-profiler](https://github.com/c4milo/v8-profiler/blob/master/binding.gyp)
* [airtunes](https://github.com/radioline/node_airtunes/blob/master/binding.gyp)
* [node-fann](https://github.com/c4milo/node-fann/blob/master/binding.gyp)
* [node-talib](https://github.com/oransel/node-talib/blob/master/binding.gyp)
* [node-leveldown](https://github.com/rvagg/node-leveldown/blob/master/binding.gyp) + [leveldb.gyp](https://github.com/rvagg/node-leveldown/blob/master/deps/leveldb/leveldb.gyp) + [snappy.gyp](https://github.com/rvagg/node-leveldown/blob/master/deps/snappy/snappy.gyp)
* [node-expat](https://github.com/astro/node-expat/blob/master/binding.gyp) + [libexpat](https://github.com/astro/node-expat/blob/master/deps/libexpat/libexpat.gyp)
* [node-openvg-canvas](https://github.com/luismreis/node-openvg-canvas/blob/master/binding.gyp) + [node-openvg](https://github.com/luismreis/node-openvg/blob/master/binding.gyp)
* [node-cryptopp](https://github.com/BatikhSouri/node-cryptopp/blob/master/binding.gyp)
* [topcube](https://github.com/creationix/topcube/blob/master/binding.gyp)
* [node-osmium](https://github.com/osmcode/node-osmium/blob/master/binding.gyp)
* [node-osrm](https://github.com/DennisOSRM/node-osrm)
* [node-oracle](https://github.com/joeferner/node-oracle/blob/master/binding.gyp)
* [node-process-list](https://github.com/ReklatsMasters/node-process-list/blob/master/binding.gyp)
* [node-nanomsg](https://github.com/nickdesaulniers/node-nanomsg/blob/master/binding.gyp)
* [Ghostscript4JS](https://github.com/NickNaso/ghostscript4js/blob/master/binding.gyp)
* [nodecv](https://github.com/xudafeng/nodecv/blob/master/binding.gyp)
* [magick-cli](https://github.com/NickNaso/magick-cli/blob/master/binding.gyp)
* [sharp](https://github.com/lovell/sharp/blob/master/binding.gyp)
* [krb5](https://github.com/adaltas/node-krb5/blob/master/binding.gyp)
* [node-heapdump](https://github.com/bnoordhuis/node-heapdump/blob/master/binding.gyp)

3
electron/node_modules/node-gyp/eslint.config.js generated vendored Normal file
View file

@ -0,0 +1,3 @@
'use strict'
module.exports = require('neostandard')({})

View file

@ -1,4 +0,0 @@
[flake8]
max-complexity = 101
max-line-length = 88
extend-ignore = E203 # whitespace before ':' to agree with psf/black

View file

@ -1,36 +0,0 @@
# TODO: Enable os: windows-latest
# TODO: Enable pytest --doctest-modules
name: Python_tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
Python_tests:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
max-parallel: 8
matrix:
os: [macos-latest, ubuntu-latest] # , windows-latest]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools
pip install --editable ".[dev]"
- run: ./gyp -V && ./gyp --version && gyp -V && gyp --version
- name: Lint with flake8
run: flake8 . --ignore=E203,W503 --max-complexity=101 --max-line-length=88 --show-source --statistics
- name: Test with pytest
run: pytest
# - name: Run doctests with pytest
# run: pytest --doctest-modules

View file

@ -1,45 +0,0 @@
name: node-gyp integration
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
integration:
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
python: ["3.7", "3.10"]
runs-on: ${{ matrix.os }}
steps:
- name: Clone gyp-next
uses: actions/checkout@v3
with:
path: gyp-next
- name: Clone nodejs/node-gyp
uses: actions/checkout@v3
with:
repository: nodejs/node-gyp
path: node-gyp
- uses: actions/setup-node@v3
with:
node-version: 14.x
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: Install dependencies
run: |
cd node-gyp
npm install --no-progress
- name: Replace gyp in node-gyp
shell: bash
run: |
rm -rf node-gyp/gyp
cp -r gyp-next node-gyp/gyp
- name: Run tests
run: |
cd node-gyp
npm test

View file

@ -1,32 +0,0 @@
name: Node.js Windows integration
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
build-windows:
runs-on: windows-2019
steps:
- name: Clone gyp-next
uses: actions/checkout@v3
with:
path: gyp-next
- name: Clone nodejs/node
uses: actions/checkout@v3
with:
repository: nodejs/node
path: node
- name: Install deps
run: choco install nasm
- name: Replace gyp in Node.js
run: |
rm -Recurse node/tools/gyp
cp -Recurse gyp-next node/tools/gyp
- name: Build Node.js
run: |
cd node
./vcbuild.bat

View file

@ -1,16 +0,0 @@
on:
push:
branches:
- main
name: release-please
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: google-github-actions/release-please-action@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
release-type: python
package-name: gyp-next
bump-minor-pre-major: true

View file

@ -0,0 +1,3 @@
{
".": "0.22.2"
}

View file

@ -1,16 +0,0 @@
# Names should be added to this file like so:
# Name or Organization <email address>
Google Inc. <*@google.com>
Bloomberg Finance L.P. <*@bloomberg.net>
IBM Inc. <*@*.ibm.com>
Yandex LLC <*@yandex-team.ru>
Steven Knight <knight@baldmt.com>
Ryan Norton <rnorton10@gmail.com>
David J. Sankel <david@sankelsoftware.com>
Eric N. Vander Weele <ericvw@gmail.com>
Tom Freudenberg <th.freudenberg@gmail.com>
Julien Brianceau <jbriance@cisco.com>
Refael Ackermann <refack@gmail.com>
Ujjwal Sharma <ryzokuken@disroot.org>

View file

@ -1,233 +0,0 @@
# Changelog
## [0.14.0](https://github.com/nodejs/gyp-next/compare/v0.13.0...v0.14.0) (2022-10-08)
### Features
* Add command line argument for `gyp --version` ([#164](https://github.com/nodejs/gyp-next/issues/164)) ([5c9f4d0](https://github.com/nodejs/gyp-next/commit/5c9f4d05678dd855e18ed2327219e5d18e5374db))
* ninja build for iOS ([#174](https://github.com/nodejs/gyp-next/issues/174)) ([b6f2714](https://github.com/nodejs/gyp-next/commit/b6f271424e0033d7ed54d437706695af2ba7a1bf))
* **zos:** support IBM Open XL C/C++ & PL/I compilers on z/OS ([#178](https://github.com/nodejs/gyp-next/issues/178)) ([43a7211](https://github.com/nodejs/gyp-next/commit/43a72110ae3fafb13c9625cc7a969624b27cda47))
### Bug Fixes
* lock windows env ([#163](https://github.com/nodejs/gyp-next/issues/163)) ([44bd0dd](https://github.com/nodejs/gyp-next/commit/44bd0ddc93ea0b5770a44dd326a2e4ae62c21442))
* move configuration information into pyproject.toml ([#176](https://github.com/nodejs/gyp-next/issues/176)) ([d69d8ec](https://github.com/nodejs/gyp-next/commit/d69d8ece6dbff7af4f2ea073c9fd170baf8cb7f7))
* node.js debugger adds stderr (but exit code is 0) -> shouldn't throw ([#179](https://github.com/nodejs/gyp-next/issues/179)) ([1a457d9](https://github.com/nodejs/gyp-next/commit/1a457d9ed08cfd30c9fa551bc5cf0d90fb583787))
## [0.13.0](https://www.github.com/nodejs/gyp-next/compare/v0.12.1...v0.13.0) (2022-05-11)
### Features
* add PRODUCT_DIR_ABS variable ([#151](https://www.github.com/nodejs/gyp-next/issues/151)) ([80d2626](https://www.github.com/nodejs/gyp-next/commit/80d26263581db829b61b312a7bdb5cc791df7824))
### Bug Fixes
* execvp: printf: Argument list too long ([#147](https://www.github.com/nodejs/gyp-next/issues/147)) ([c4e14f3](https://www.github.com/nodejs/gyp-next/commit/c4e14f301673fadbac3ab7882d0b5f4d02530cb9))
### [0.12.1](https://www.github.com/nodejs/gyp-next/compare/v0.12.0...v0.12.1) (2022-04-06)
### Bug Fixes
* **msvs:** avoid fixing path for arguments with "=" ([#143](https://www.github.com/nodejs/gyp-next/issues/143)) ([7e8f16e](https://www.github.com/nodejs/gyp-next/commit/7e8f16eb165e042e64bec98fa6c2a0232a42c26b))
## [0.12.0](https://www.github.com/nodejs/gyp-next/compare/v0.11.0...v0.12.0) (2022-04-04)
### Features
* support building shared libraries on z/OS ([#137](https://www.github.com/nodejs/gyp-next/issues/137)) ([293bcfa](https://www.github.com/nodejs/gyp-next/commit/293bcfa4c25c6adb743377adafc45a80fee492c6))
## [0.11.0](https://www.github.com/nodejs/gyp-next/compare/v0.10.1...v0.11.0) (2022-03-04)
### Features
* Add proper support for IBM i ([#140](https://www.github.com/nodejs/gyp-next/issues/140)) ([fdda4a3](https://www.github.com/nodejs/gyp-next/commit/fdda4a3038b8a7042ad960ce7a223687c24a21b1))
### [0.10.1](https://www.github.com/nodejs/gyp-next/compare/v0.10.0...v0.10.1) (2021-11-24)
### Bug Fixes
* **make:** only generate makefile for multiple toolsets if requested ([#133](https://www.github.com/nodejs/gyp-next/issues/133)) ([f463a77](https://www.github.com/nodejs/gyp-next/commit/f463a77705973289ea38fec1b244c922ac438e26))
## [0.10.0](https://www.github.com/nodejs/gyp-next/compare/v0.9.6...v0.10.0) (2021-08-26)
### Features
* **msvs:** add support for Visual Studio 2022 ([#124](https://www.github.com/nodejs/gyp-next/issues/124)) ([4bd9215](https://www.github.com/nodejs/gyp-next/commit/4bd9215c44d300f06e916aec1d6327c22b78272d))
### [0.9.6](https://www.github.com/nodejs/gyp-next/compare/v0.9.5...v0.9.6) (2021-08-23)
### Bug Fixes
* align flake8 test ([#122](https://www.github.com/nodejs/gyp-next/issues/122)) ([f1faa8d](https://www.github.com/nodejs/gyp-next/commit/f1faa8d3081e1a47e917ff910892f00dff16cf8a))
* **msvs:** fix paths again in action command arguments ([#121](https://www.github.com/nodejs/gyp-next/issues/121)) ([7159dfb](https://www.github.com/nodejs/gyp-next/commit/7159dfbc5758c9ec717e215f2c36daf482c846a1))
### [0.9.5](https://www.github.com/nodejs/gyp-next/compare/v0.9.4...v0.9.5) (2021-08-18)
### Bug Fixes
* add python 3.6 to node-gyp integration test ([3462d4c](https://www.github.com/nodejs/gyp-next/commit/3462d4ce3c31cce747513dc7ca9760c81d57c60e))
* revert for windows compatibility ([d078e7d](https://www.github.com/nodejs/gyp-next/commit/d078e7d7ae080ddae243188f6415f940376a7368))
* support msvs_quote_cmd in ninja generator ([#117](https://www.github.com/nodejs/gyp-next/issues/117)) ([46486ac](https://www.github.com/nodejs/gyp-next/commit/46486ac6e9329529d51061e006a5b39631e46729))
### [0.9.4](https://www.github.com/nodejs/gyp-next/compare/v0.9.3...v0.9.4) (2021-08-09)
### Bug Fixes
* .S is an extension for asm file on Windows ([#115](https://www.github.com/nodejs/gyp-next/issues/115)) ([d2fad44](https://www.github.com/nodejs/gyp-next/commit/d2fad44ef3a79ca8900f1307060153ded57053fc))
### [0.9.3](https://www.github.com/nodejs/gyp-next/compare/v0.9.2...v0.9.3) (2021-07-07)
### Bug Fixes
* build failure with ninja and Python 3 on Windows ([#113](https://www.github.com/nodejs/gyp-next/issues/113)) ([c172d10](https://www.github.com/nodejs/gyp-next/commit/c172d105deff5db4244e583942215918fa80dd3c))
### [0.9.2](https://www.github.com/nodejs/gyp-next/compare/v0.9.1...v0.9.2) (2021-05-21)
### Bug Fixes
* add support of utf8 encoding ([#105](https://www.github.com/nodejs/gyp-next/issues/105)) ([4d0f93c](https://www.github.com/nodejs/gyp-next/commit/4d0f93c249286d1f0c0f665f5fe7346119f98cf1))
### [0.9.1](https://www.github.com/nodejs/gyp-next/compare/v0.9.0...v0.9.1) (2021-05-14)
### Bug Fixes
* py lint ([3b6a8ee](https://www.github.com/nodejs/gyp-next/commit/3b6a8ee7a66193a8a6867eba9e1d2b70bdf04402))
## [0.9.0](https://www.github.com/nodejs/gyp-next/compare/v0.8.1...v0.9.0) (2021-05-13)
### Features
* use LDFLAGS_host for host toolset ([#98](https://www.github.com/nodejs/gyp-next/issues/98)) ([bea5c7b](https://www.github.com/nodejs/gyp-next/commit/bea5c7bd67d6ad32acbdce79767a5481c70675a2))
### Bug Fixes
* msvs.py: remove overindentation ([#102](https://www.github.com/nodejs/gyp-next/issues/102)) ([3f83e99](https://www.github.com/nodejs/gyp-next/commit/3f83e99056d004d9579ceb786e06b624ddc36529))
* update gyp.el to change case to cl-case ([#93](https://www.github.com/nodejs/gyp-next/issues/93)) ([13d5b66](https://www.github.com/nodejs/gyp-next/commit/13d5b66aab35985af9c2fb1174fdc6e1c1407ecc))
### [0.8.1](https://www.github.com/nodejs/gyp-next/compare/v0.8.0...v0.8.1) (2021-02-18)
### Bug Fixes
* update shebang lines from python to python3 ([#94](https://www.github.com/nodejs/gyp-next/issues/94)) ([a1b0d41](https://www.github.com/nodejs/gyp-next/commit/a1b0d4171a8049a4ab7a614202063dec332f2df4))
## [0.8.0](https://www.github.com/nodejs/gyp-next/compare/v0.7.0...v0.8.0) (2021-01-15)
### ⚠ BREAKING CHANGES
* remove support for Python 2
### Bug Fixes
* revert posix build job ([#86](https://www.github.com/nodejs/gyp-next/issues/86)) ([39dc34f](https://www.github.com/nodejs/gyp-next/commit/39dc34f0799c074624005fb9bbccf6e028607f9d))
### gyp
* Remove support for Python 2 ([#88](https://www.github.com/nodejs/gyp-next/issues/88)) ([22e4654](https://www.github.com/nodejs/gyp-next/commit/22e465426fd892403c95534229af819a99c3f8dc))
## [0.7.0](https://www.github.com/nodejs/gyp-next/compare/v0.6.2...v0.7.0) (2020-12-17)
### ⚠ BREAKING CHANGES
* **msvs:** On Windows, arguments passed to the "action" commands are no longer transformed to replace slashes with backslashes.
### Features
* **xcode:** --cross-compiling overrides arch-specific settings ([973bae0](https://www.github.com/nodejs/gyp-next/commit/973bae0b7b08be7b680ecae9565fbd04b3e0787d))
### Bug Fixes
* **msvs:** do not fix paths in action command arguments ([fc22f83](https://www.github.com/nodejs/gyp-next/commit/fc22f8335e2016da4aae4f4233074bd651d2faea))
* cmake on python 3 ([fd61f5f](https://www.github.com/nodejs/gyp-next/commit/fd61f5faa5275ec8fc98e3c7868c0dd46f109540))
* ValueError: invalid mode: 'rU' while trying to load binding.gyp ([d0504e6](https://www.github.com/nodejs/gyp-next/commit/d0504e6700ce48f44957a4d5891b142a60be946f))
* xcode cmake parsing ([eefe8d1](https://www.github.com/nodejs/gyp-next/commit/eefe8d10e99863bc4ac7e2ed32facd608d400d4b))
### [0.6.2](https://www.github.com/nodejs/gyp-next/compare/v0.6.1...v0.6.2) (2020-10-16)
### Bug Fixes
* do not rewrite absolute paths to avoid long paths ([#74](https://www.github.com/nodejs/gyp-next/issues/74)) ([c2ccc1a](https://www.github.com/nodejs/gyp-next/commit/c2ccc1a81f7f94433a94f4d01a2e820db4c4331a))
* only include MARMASM when toolset is target ([5a2794a](https://www.github.com/nodejs/gyp-next/commit/5a2794aefb58f0c00404ff042b61740bc8b8d5cd))
### [0.6.1](https://github.com/nodejs/gyp-next/compare/v0.6.0...v0.6.1) (2020-10-14)
### Bug Fixes
* Correctly rename object files for absolute paths in MSVS generator.
## [0.6.0](https://github.com/nodejs/gyp-next/compare/v0.5.0...v0.6.0) (2020-10-13)
### Features
* The Makefile generator will now output shared libraries directly to the product directory on all platforms (previously only macOS).
## [0.5.0](https://github.com/nodejs/gyp-next/compare/v0.4.0...v0.5.0) (2020-09-30)
### Features
* Extended compile_commands_json generator to consider more file extensions than just `c` and `cc`. `cpp` and `cxx` are now supported.
* Source files with duplicate basenames are now supported.
### Removed
* The `--no-duplicate-basename-check` option was removed.
* The `msvs_enable_marmasm` configuration option was removed in favor of auto-inclusion of the "marmasm" sections for Windows on ARM.
## [0.4.0](https://github.com/nodejs/gyp-next/compare/v0.3.0...v0.4.0) (2020-07-14)
### Features
* Added support for passing arbitrary architectures to Xcode builds, enables `arm64` builds.
### Bug Fixes
* Fixed a bug on Solaris where copying archives failed.
## [0.3.0](https://github.com/nodejs/gyp-next/compare/v0.2.1...v0.3.0) (2020-06-06)
### Features
* Added support for MSVC cross-compilation. This allows compilation on x64 for a Windows ARM target.
### Bug Fixes
* Fixed XCode CLT version detection on macOS Catalina.
### [0.2.1](https://github.com/nodejs/gyp-next/compare/v0.2.0...v0.2.1) (2020-05-05)
### Bug Fixes
* Relicensed to Node.js contributors.
* Fixed Windows bug introduced in v0.2.0.
## [0.2.0](https://github.com/nodejs/gyp-next/releases/tag/v0.2.0) (2020-04-06)
This is the first release of this project, based on https://chromium.googlesource.com/external/gyp with changes made over the years in Node.js and node-gyp.

View file

@ -1,32 +0,0 @@
# Contributing to gyp-next
## Code of Conduct
This project is bound to the [Node.js Code of Conduct](https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md).
<a id="developers-certificate-of-origin"></a>
## Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
* (a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
* (b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
* (c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
* (d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.

View file

@ -1,7 +0,0 @@
GYP can Generate Your Projects.
===================================
Documents are available at [gyp.gsrc.io](https://gyp.gsrc.io), or you can check out ```md-pages``` branch to read those documents offline.
__gyp-next__ is [released](https://github.com/nodejs/gyp-next/releases) to the [__Python Packaging Index__](https://pypi.org/project/gyp-next) (PyPI) and can be installed with the command:
* `python3 -m pip install gyp-next`

View file

@ -0,0 +1,4 @@
rule cc
command = cc $in $out
build my.out: cc my.in

116
electron/node_modules/node-gyp/gyp/docs/GypVsCMake.md generated vendored Normal file
View file

@ -0,0 +1,116 @@
# vs. CMake
GYP was originally created to generate native IDE project files (Visual Studio, Xcode) for building [Chromium](http://www.chromim.org).
The functionality of GYP is very similar to the [CMake](http://www.cmake.org)
build tool. Bradley Nelson wrote up the following description of why the team
created GYP instead of using CMake. The text below is copied from
http://www.mail-archive.com/webkit-dev@lists.webkit.org/msg11029.html
```
Re: [webkit-dev] CMake as a build system?
Bradley Nelson
Mon, 19 Apr 2010 22:38:30 -0700
Here's the innards of an email with a laundry list of stuff I came up with a
while back on the gyp-developers list in response to Mike Craddick regarding
what motivated gyp's development, since we were aware of cmake at the time
(we'd even started a speculative port):
I did an exploratory port of portions of Chromium to cmake (I think I got as
far as net, base, sandbox, and part of webkit).
There were a number of motivations, not all of which would apply to other
projects. Also, some of the design of gyp was informed by experience at
Google with large projects built wholly from source, leading to features
absent from cmake, but not strictly required for Chromium.
1. Ability to incrementally transition on Windows. It took us about 6 months
to switch fully to gyp. Previous attempts to move to scons had taken a long
time and failed, due to the requirement to transition while in flight. For a
substantial period of time, we had a hybrid of checked in vcproj and gyp generated
vcproj. To this day we still have a good number of GUIDs pinned in the gyp files,
because different parts of our release pipeline have leftover assumptions
regarding manipulating the raw sln/vcprojs. This transition occurred from
the bottom up, largely because modules like base were easier to convert, and
had a lower churn rate. During early stages of the transition, the majority
of the team wasn't even aware they were using gyp, as it integrated into
their existing workflow, and only affected modules that had been converted.
2. Generation of a more 'normal' vcproj file. Gyp attempts, particularly on
Windows, to generate vcprojs which resemble hand generated projects. It
doesn't generate any Makefile type projects, but instead produces msvs
Custom Build Steps and Custom Build Rules. This makes the resulting projects
easier to understand from the IDE and avoids parts of the IDE that simply
don't function correctly if you use Makefile projects. Our early hope with
gyp was to support the least common denominator of features present in each
of the platform specific project file formats, rather than falling back on
generated Makefiles/shell scripts to emulate some common abstraction. CMake by
comparison makes a good faith attempt to use native project features, but
falls back on generated scripts in order to preserve the same semantics on
each platforms.
3. Abstraction on the level of project settings, rather than command line
flags. In gyp's syntax you can add nearly any option present in a hand
generated xcode/vcproj file. This allows you to use abstractions built into
the IDEs rather than reverse engineering them possibly incorrectly for
things like: manifest generation, precompiled headers, bundle generation.
When somebody wants to use a particular menu option from msvs, I'm able to
do a web search on the name of the setting from the IDE and provide them
with a gyp stanza that does the equivalent. In many cases, not all project
file constructs correspond to command line flags.
4. Strong notion of module public/private interface. Gyp allows targets to
publish a set of direct_dependent_settings, specifying things like
include_dirs, defines, platforms specific settings, etc. This means that
when module A depends on module B, it automatically acquires the right build
settings without module A being filled with assumptions/knowledge of exactly
how module B is built. Additionally, all of the transitive dependencies of
module B are pulled in. This avoids their being a single top level view of
the project, rather each gyp file expresses knowledge about its immediate
neighbors. This keep local knowledge local. CMake effectively has a large
shared global namespace.
5. Cross platform generation. CMake is not able to generate all project
files on all platforms. For example xcode projects cannot be generated from
windows (cmake uses mac specific libraries to do project generation). This
means that for instance generating a tarball containing pregenerated
projects for all platforms is hard with Cmake (requires distribution to
several machine types).
6. Gyp has rudimentary cross compile support. Currently we've added enough
functionality to gyp to support x86 -> arm cross compiles. Last I checked
this functionality wasn't present in cmake. (This occurred later).
That being said there are a number of drawbacks currently to gyp:
1. Because platform specific settings are expressed at the project file
level (rather than the command line level). Settings which might otherwise
be shared in common between platforms (flags to gcc on mac/linux), end up
being repeated twice. Though in fairness there is actually less sharing here
than you'd think. include_dirs and defines actually represent 90% of what
can be typically shared.
2. CMake may be more mature, having been applied to a broader range of
projects. There a number of 'tool modules' for cmake, which are shared in a
common community.
3. gyp currently makes some nasty assumptions about the availability of
chromium's hermetic copy of cygwin on windows. This causes you to either
have to special case a number of rules, or swallow this copy of cygwin as a
build time dependency.
4. CMake includes a fairly readable imperative language. Currently Gyp has a
somewhat poorly specified declarative language (variable expansion happens
in sometimes weird and counter-intuitive ways). In fairness though, gyp assumes
that external python scripts can be used as an escape hatch. Also gyp avoids
a lot of the things you'd need imperative code for, by having a nice target
settings publication mechanism.
5. (Feature/drawback depending on personal preference). Gyp's syntax is
DEEPLY nested. It suffers from all of Lisp's advantages and drawbacks.
-BradN
```

46
electron/node_modules/node-gyp/gyp/docs/Hacking.md generated vendored Normal file
View file

@ -0,0 +1,46 @@
# Hacking
## Getting the sources
Git is required to hack on anything, you can set up a git clone of GYP
as follows:
```
mkdir foo
cd foo
git clone git@github.com:nodejs/gyp-next.git
cd gyp
```
(this will clone gyp underneath it into `foo/gyp`.
`foo` can be any directory name you want. Once you've done that,
you can use the repo like anything other Git repo.
## Testing your change
GYP has a suite of tests which you can run with the provided test driver
to make sure your changes aren't breaking anything important.
You run the test driver with e.g.
``` sh
$ python -m pip install --upgrade pip
$ pip install --editable ".[dev]"
$ python -m pytest
```
See [Testing](Testing.md) for more details on the test framework.
Note that it can be handy to look at the project files output by the tests
to diagnose problems. The easiest way to do that is by kindly asking the
test driver to leave the temporary directories it creates in-place.
This is done by setting the environment variable "PRESERVE", e.g.
```
set PRESERVE=all # On Windows
export PRESERVE=all # On saner platforms.
```
## Reviewing your change
All changes to GYP must be code reviewed before submission.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,430 @@
# Language Specification
## Objective
Create a tool for the Chromium project that generates native Visual Studio,
Xcode and SCons and/or make build files from a platform-independent input
format. Make the input format as reasonably general as possible without
spending extra time trying to "get everything right," except where not doing so
would likely lead Chromium to an eventual dead end. When in doubt, do what
Chromium needs and don't worry about generalizing the solution.
## Background
Numerous other projects, both inside and outside Google, have tried to
create a simple, universal cross-platform build representation that
still allows sufficient per-platform flexibility to accommodate
irreconcilable differences. The fact that no obvious working candidate
exists that meets Chromium's requirements indicates this is probably a
tougher problem than it appears at first glance. We aim to succeed by
creating a tool that is highly specific to Chromium's specific use case,
not to the general case of design a completely platform-independent tool
for expressing any possible build.
The Mac has the most sophisticated model for application development
through an IDE. Consequently, we will use the Xcode model as the
starting point (the input file format must handle Chromium's use of
Xcode seamlessly) and adapt the design as necessary for the other
platforms.
## Overview
The overall design has the following characteristics:
* Input configurations are specified in files with the suffix `.gyp`.
* Each `.gyp` file specifies how to build the targets for the
"component" defined by that file.
* Each `.gyp` file generates one or more output files appropriate to
the platform:
* On Mac, a `.gyp` file generates one Xcode .xcodeproj bundle with
information about how its targets are built.
* On Windows, a `.gyp` file generates one Visual Studio .sln file,
and one Visual Studio .vcproj file per target.
* On Linux, a `.gyp` file generates one SCons file and/or one
Makefile per target
* The `.gyp` file syntax is a Python data structure.
* Use of arbitrary Python in `.gyp` files is forbidden.
* Use of eval() with restricted globals and locals on `.gyp` file
contents restricts the input to an evaluated expression, not
arbitrary Python statements.
* All input is expected to comply with JSON, with two exceptions:
the # character (not inside strings) begins a comment that lasts
until the end of the line, and trailing commas are permitted at
the end of list and dict contents.
* Input data is a dictionary of keywords and values.
* "Invalid" keywords on any given data structure are not illegal,
they're just ignored.
* TODO: providing warnings on use of illegal keywords would help
users catch typos. Figure out something nice to do with this.
## Detailed Design
Some up-front design principles/thoughts/TODOs:
* Re-use keywords consistently.
* Keywords that allow configuration of a platform-specific concept get
prefixed appropriately:
* Examples: `msvs_disabled_warnings`, `xcode_framework_dirs`
* The input syntax is declarative and data-driven.
* This gets enforced by using Python `eval()` (which only evaluates
an expression) instead of `exec` (which executes arbitrary python)
* Semantic meanings of specific keyword values get deferred until all
are read and the configuration is being evaluated to spit out the
appropriate file(s)
* Source file lists:
* Are flat lists. Any imposed ordering within the `.gyp` file (e.g.
alphabetically) is purely by convention and for developer
convenience. When source files are linked or archived together,
it is expected that this will occur in the order that files are
listed in the `.gyp` file.
* Source file lists contain no mechanism for by-hand folder
configuration (`Filter` tags in Visual Studio, `Groups` in Xcode)
* A folder hierarchy is created automatically that mirrors the file
system
### Example
```
{
'target_defaults': {
'defines': [
'U_STATIC_IMPLEMENTATION',
['LOGFILE', 'foo.log',],
],
'include_dirs': [
'..',
],
},
'targets': [
{
'target_name': 'foo',
'type': 'static_library',
'sources': [
'foo/src/foo.cc',
'foo/src/foo_main.cc',
],
'include_dirs': [
'foo',
'foo/include',
],
'conditions': [
[ 'OS==mac', { 'sources': [ 'platform_test_mac.mm' ] } ]
],
'direct_dependent_settings': {
'defines': [
'UNIT_TEST',
],
'include_dirs': [
'foo',
'foo/include',
],
},
},
],
}
```
### Structural Elements
### Top-level Dictionary
This is the single dictionary in the `.gyp` file that defines the
targets and how they're to be built.
The following keywords are meaningful within the top-level dictionary
definition:
| *Keyword* | *Description* |
|:------------------|:------------------|
| `conditions` | A conditional section that may contain other items that can be present in a top-level dictionary, on a conditional basis. See the "Conditionals" section below. |
| `includes` | A list of `.gypi` files to be included in the top-level dictionary. |
| `target_defaults` | A dictionary of default settings to be inherited by all targets in the top-level dictionary. See the "Settings keywords" section below. |
| `targets` | A list of target specifications. See the "targets" below. |
| `variables` | A dictionary containing variable definitions. Each key in this dictionary is the name of a variable, and each value must be a string value that the variable is to be set to. |
### targets
A list of dictionaries defining targets to be built by the files
generated from this `.gyp` file.
Targets may contain `includes`, `conditions`, and `variables` sections
as permitted in the root dictionary. The following additional keywords
have structural meaning for target definitions:
| *Keyword* | *Description* |
|:---------------------------- |:------------------------------------------|
| `actions` | A list of special custom actions to perform on a specific input file, or files, to produce output files. See the "Actions" section below. |
| `all_dependent_settings` | A dictionary of settings to be applied to all dependents of the target, transitively. This includes direct dependents and the entire set of their dependents, and so on. This section may contain anything found within a `target` dictionary, except `configurations`, `target_name`, and `type` sections. Compare `direct_dependent_settings` and `link_settings`. |
| `configurations` | A list of dictionaries defining build configurations for the target. See the "Configurations" section below. |
| `copies` | A list of copy actions to perform. See the "Copies" section below. |
| `defines` | A list of preprocessor definitions to be passed on the command line to the C/C++ compiler (via `-D` or `/D` options). |
| `dependencies` | A list of targets on which this target depends. Targets in other `.gyp` files are specified as `../path/to/other.gyp:target_we_want`. |
| `direct_dependent_settings` | A dictionary of settings to be applied to other targets that depend on this target. These settings will only be applied to direct dependents. This section may contain anything found within a `target` dictionary, except `configurations`, `target_name`, and `type` sections. Compare with `all_dependent_settings` and `link_settings`. |
| `include_dirs` | A list of include directories to be passed on the command line to the C/C++ compiler (via `-I` or `/I` options). |
| `libraries` | A list of list of libraries (and/or frameworks) on which this target depends. |
| `link_settings` | A dictionary of settings to be applied to targets in which this target's contents are linked. `executable` and `shared_library` targets are linkable, so if they depend on a non-linkable target such as a `static_library`, they will adopt its `link_settings`. This section can contain anything found within a `target` dictionary, except `configurations`, `target_name`, and `type` sections. Compare `all_dependent_settings` and `direct_dependent_settings`. |
| `rules` | A special custom action to perform on a list of input files, to produce output files. See the "Rules" section below. |
| `sources` | A list of source files that are used to build this target or which should otherwise show up in the IDE for this target. In practice, we expect this list to be a union of all files necessary to build the target on all platforms, as well as other related files that aren't actually used for building, like README files. |
| `target_conditions` | Like `conditions`, but evaluation is delayed until the settings have been merged into an actual target. `target_conditions` may be used to place conditionals into a `target_defaults` section but have them still depend on specific target settings. |
| `target_name` | The name of a target being defined. |
| `type` | The type of target being defined. This field currently supports `executable`, `static_library`, `shared_library`, and `none`. The `none` target type is useful when producing output which is not linked. For example, converting raw translation files into resources or documentation into platform specific help files. |
| `msvs_props` | A list of Visual Studio property sheets (`.vsprops` files) to be used to build the target. |
| `xcode_config_file` | An Xcode configuration (`.xcconfig` file) to be used to build the target. |
| `xcode_framework_dirs` | A list of framework directories be used to build the target. |
You can affect the way that lists/dictionaries are merged together (for
example the way a list in target\_defaults interacts with the same named
list in the target itself) with a couple of special characters, which
are covered in [Merge
Basics](InputFormatReference#Merge_Basics_(=,_?,_+).md) and [List
Filters](InputFormatReference#List_Filters.md) on the
InputFormatReference page.
### configurations
`configurations` sections may be found within `targets` or
`target_defaults` sections. The `configurations` section is a list of
dictionaries specifying different build configurations. Because
configurations are implemented as lists, it is not currently possible to
override aspects of configurations that are imported into a target from
a `target_defaults` section.
NOTE: It is extremely important that each target within a project define
the same set of configurations. This continues to apply even when a
project spans across multiple `.gyp` files.
A configuration dictionary may contain anything that can be found within
a target dictionary, except for `actions`, `all_dependent_settings`,
`configurations`, `dependencies`, `direct_dependent_settings`,
`libraries`, `link_settings`, `sources`, `target_name`, and `type`.
Configuration dictionaries may also contain these elements:
| *Keyword* | *Description* |
|:---------------------|:----------------------------------------------------|
| `configuration_name` | Required attribute. The name of the configuration. |
### Conditionals
Conditionals may appear within any dictionary in a `.gyp` file. There
are two tpes of conditionals, which differ only in the timing of their
processing. `conditions` sections are processed shortly after loading
`.gyp` files, and `target_conditions` sections are processed after all
dependencies have been computed.
A conditional section is introduced with a `conditions` or
`target_conditions` dictionary keyword, and is composed of a list. Each
list contains two or three elements. The first two elements, which are
always required, are the conditional expression to evaluate and a
dictionary containing settings to merge into the dictionary containing
the `conditions` or `target_conditions` section if the expression
evaluates to true. The third, optional, list element is a dictionary to
merge if the expression evaluates to false.
The `eval()` of the expression string takes place in the context of
global and/or local dictionaries that constructed from the `.gyp` input
data, and overrides the `__builtin__` dictionary, to prevent the
execution of arbitrary Python code.
### Actions
An `actions` section provides a list of custom build actions to perform
on inputs, producing outputs. The `actions` section is organized as a
list. Each item in the list is a dictionary having the following form:
| *Keyword* | *Type* | *Description* |
|:--------------|:-------|:-----------------------------|
| `action_name` | string | The name of the action. Depending on how actions are implemented in the various generators, some may desire or require this property to be set to a unique name; others may ignore this property entirely. |
| `inputs` | list | A list of pathnames treated as inputs to the custom action. |
| `outputs` | list | A list of pathnames that the custom action produces. |
| `action` | list | A command line invocation used to produce `outputs` from `inputs`. For maximum cross-platform compatibility, invocations that require a Python interpreter should be specified with a first element `"python"`. This will enable generators for environments with specialized Python installations to be able to perform the action in an appropriate Python environment. |
| `message` | string | A message to be displayed to the user by the build system when the action is run. |
Build environments will compare `inputs` and `outputs`. If any `output`
is missing or is outdated relative to any `input`, the custom action
will be invoked. If all `outputs` are present and newer than all
`inputs`, the `outputs` are considered up-to-date and the action need
not be invoked.
Actions are implemented in Xcode as shell script build phases performed
prior to the compilation phase. In the Visual Studio generator, actions
appear files with a `FileConfiguration` containing a custom
`VCCustomBuildTool` specifying the remainder of the inputs, the outputs,
and the action.
Combined with variable expansions, actions can be quite powerful. Here
is an example action that leverages variable expansions to minimize
duplication of pathnames:
```
'sources': [
# libraries.cc is generated by the js2c action below.
'<(INTERMEDIATE_DIR)/libraries.cc',
],
'actions': [
{
'variables': {
'core_library_files': [
'src/runtime.js',
'src/v8natives.js',
'src/macros.py',
],
},
'action_name': 'js2c',
'inputs': [
'tools/js2c.py',
'<@(core_library_files)',
],
'outputs': [
'<(INTERMEDIATE_DIR)/libraries.cc',
'<(INTERMEDIATE_DIR)/libraries-empty.cc',
],
'action': ['python', 'tools/js2c.py', '<@(_outputs)', 'CORE', '<@(core_library_files)'],
},
],
```
### Rules
A `rules` section provides custom build action to perform on inputs, producing
outputs. The `rules` section is organized as a list. Each item in the list is
a dictionary having the following form:
| *Keyword* | *Type* | *Description* |
|:------------|:-------|:-----------------------------------------|
| `rule_name` | string | The name of the rule. Depending on how Rules are implemented in the various generators, some may desire or require this property to be set to a unique name; others may ignore this property entirely. |
| `extension` | string | All source files of the current target with the given extension will be treated successively as inputs to the rule. |
| `inputs` | list | Additional dependencies of the rule. |
| `outputs` | list | A list of pathnames that the rule produces. Has access to `RULE_INPUT_` variables (see below). |
| `action` | list | A command line invocation used to produce `outputs` from `inputs`. For maximum cross-platform compatibility, invocations that require a Python interpreter should be specified with a first element `"python"`. This will enable generators for environments with specialized Python installations to be able to perform the action in an appropriate Python environment. Has access to `RULE_INPUT_` variables (see below). |
| `message` | string | A message to be displayed to the user by the build system when the action is run. Has access to `RULE_INPUT_` variables (see below). |
There are several variables available to `outputs`, `action`, and `message`.
| *Variable* | *Description* |
|:---------------------|:------------------------------------|
| `RULE_INPUT_PATH` | The full path to the current input. |
| `RULE_INPUT_DIRNAME` | The directory of the current input. |
| `RULE_INPUT_NAME` | The file name of the current input. |
| `RULE_INPUT_ROOT` | The file name of the current input without extension. |
| `RULE_INPUT_EXT` | The file name extension of the current input. |
Rules can be thought of as Action generators. For each source selected
by `extension` an special action is created. This action starts out with
the same `inputs`, `outputs`, `action`, and `message` as the rule. The
source is added to the action's `inputs`. The `outputs`, `action`, and
`message` are then handled the same but with the additional variables.
If the `_output` variable is used in the `action` or `message` the
`RULE_INPUT_` variables in `output` will be expanded for the current
source.
### Copies
A `copies` section provides a simple means of copying files. The
`copies` section is organized as a list. Each item in the list is a
dictionary having the following form:
| *Keyword* | *Type* | *Description* |
|:--------------|:-------|:------------------------------|
| `destination` | string | The directory into which the `files` will be copied. |
| `files` | list | A list of files to be copied. |
The copies will be created in `destination` and have the same file name
as the file they are copied from. Even if the `files` are from multiple
directories they will all be copied into the `destination` directory.
Each `destination` file has an implicit build dependency on the file it
is copied from.
### Generated Xcode .pbxproj Files
We derive the following things in a `project.pbxproj` plist file within
an `.xcodeproj` bundle from the above input file formats as follows:
* `Group hierarchy`: This is generated in a fixed format with contents
derived from the input files. There is no provision for the user to
specify additional groups or create a custom hierarchy.
* `Configuration group`: This will be used with the
`xcode_config_file` property above, if needed.
* `Source group`: The union of the `sources` lists of all `targets`
after applying appropriate `conditions`. The resulting list is
sorted and put into a group hierarchy that matches the layout of
the directory tree on disk, with a root of // (the top of the
hierarchy).
* `Frameworks group`: Taken directly from `libraries` value for the
target, after applying appropriate conditions.
* `Projects group`: References to other `.xcodeproj` bundles that
are needed by the `.xcodeproj` in which the group is contained.
* `Products group`: Output from the various targets.
* `Project References`:
* `Project Configurations`:
* Per-`.xcodeproj` file settings are not supported, all settings are
applied at the target level.
* `Targets`:
* `Phases`: Copy sources, link with libraries/frameworks, ...
* `Target Configurations`: Specified by input.
* `Dependencies`: (local and remote)
### Generated Visual Studio .vcproj Files
We derive the following sections in a `.vcproj` file from the above
input file formats as follows:
* `VisualStudioProject`:
* `Platforms`:
* `ToolFiles`:
* `Configurations`:
* `Configuration`:
* `References`:
* `Files`:
* `Filter`:
* `File`:
* `FileConfiguration`:
* `Tool`:
* `Globals`:
### Generated Visual Studio .sln Files
We derive the following sections in a `.sln` file from the above input
file formats as follows:
* `Projects`:
* `WebsiteProperties`:
* `ProjectDependencies`:
* `Global`:
* `SolutionConfigurationPlatforms`:
* `ProjectConfigurationPlatforms`:
* `SolutionProperties`:
* `NestedProjects`:
## Caveats
Notes/Question from very first prototype draft of the language.
Make sure these issues are addressed somewhere before deleting.
* Libraries are easy, application abstraction is harder
* Applications involves resource compilation
* Applications involve many inputs
* Applications include transitive closure of dependencies
* Specific use cases like cc\_library
* Mac compiles more than just .c/.cpp files (specifically, .m and .mm
files)
* Compiler options vary by:
* File type
* Target type
* Individual file
* Files may have custom settings per file per platform, but we probably
don't care or need to support this in gyp.
* Will all linked non-Chromium projects always use the same versions of every
subsystem?
* Variants are difficult. We've identified the following variants (some
specific to Chromium, some typical of other projects in the same ballpark):
* Target platform
* V8 vs. JSC
* Debug vs. Release
* Toolchain (VS version, gcc, version)
* Host platform
* L10N
* Vendor
* Purify / Valgrind
* Will everyone upgrade VS at once?
* What does a dylib dependency mean?

27
electron/node_modules/node-gyp/gyp/docs/README.md generated vendored Normal file
View file

@ -0,0 +1,27 @@
# Generate Your Projects (gyp-next)
GYP is a Meta-Build system: a build system that generates other build systems.
* [User documentation](./UserDocumentation.md)
* [Input Format Reference](./InputFormatReference.md)
* [Language specification](./LanguageSpecification.md)
* [Hacking](./Hacking.md)
* [Testing](./Testing.md)
* [GYP vs. CMake](./GypVsCMake.md)
GYP is intended to support large projects that need to be built on multiple
platforms (e.g., Mac, Windows, Linux), and where it is important that
the project can be built using the IDEs that are popular on each platform
as if the project is a "native" one.
It can be used to generate XCode projects, Visual Studio projects, Ninja
build files, and Makefiles. In each case GYP's goal is to replicate as
closely as possible the way one would set up a native build of the project
using the IDE.
GYP can also be used to generate "hybrid" projects that provide the IDE
scaffolding for a nice user experience but call out to Ninja to do the actual
building (which is usually much faster than the native build systems of the
IDEs).
For more information on GYP, click on the links above.

450
electron/node_modules/node-gyp/gyp/docs/Testing.md generated vendored Normal file
View file

@ -0,0 +1,450 @@
# Testing
NOTE: this document is outdated and needs to be updated. Read with your own discretion.
## Introduction
This document describes the GYP testing infrastructure,
as provided by the `TestGyp.py` module.
These tests emphasize testing the _behavior_ of the
various GYP-generated build configurations:
Visual Studio, Xcode, SCons, Make, etc.
The goal is _not_ to test the output of the GYP generators by,
for example, comparing a GYP-generated Makefile
against a set of known "golden" Makefiles
(although the testing infrastructure could
be used to write those kinds of tests).
The idea is that the generated build configuration files
could be completely written to add a feature or fix a bug
so long as they continue to support the functional behaviors
defined by the tests: building programs, shared libraries, etc.
## "Hello, world!" GYP test configuration
Here is an actual test configuration,
a simple build of a C program to print `"Hello, world!"`.
```
$ ls -l test/hello
total 20
-rw-r--r-- 1 knight knight 312 Jul 30 20:22 gyptest-all.py
-rw-r--r-- 1 knight knight 307 Jul 30 20:22 gyptest-default.py
-rwxr-xr-x 1 knight knight 326 Jul 30 20:22 gyptest-target.py
-rw-r--r-- 1 knight knight 98 Jul 30 20:22 hello.c
-rw-r--r-- 1 knight knight 142 Jul 30 20:22 hello.gyp
$
```
The `gyptest-*.py` files are three separate tests (test scripts)
that use this configuration. The first one, `gyptest-all.py`,
looks like this:
```
#!/usr/bin/env python
"""
Verifies simplest-possible build of a "Hello, world!" program
using an explicit build target of 'all'.
"""
import TestGyp
test = TestGyp.TestGyp()
test.run_gyp('hello.gyp')
test.build_all('hello.gyp')
test.run_built_executable('hello', stdout="Hello, world!\n")
test.pass_test()
```
The test script above runs GYP against the specified input file
(`hello.gyp`) to generate a build configuration.
It then tries to build the `'all'` target
(or its equivalent) using the generated build configuration.
Last, it verifies that the build worked as expected
by running the executable program (`hello`)
that was just presumably built by the generated configuration,
and verifies that the output from the program
matches the expected `stdout` string (`"Hello, world!\n"`).
Which configuration is generated
(i.e., which build tool to test)
is specified when the test is run;
see the next section.
Surrounding the functional parts of the test
described above are the header,
which should be basically the same for each test
(modulo a different description in the docstring):
```
#!/usr/bin/env python
"""
Verifies simplest-possible build of a "Hello, world!" program
using an explicit build target of 'all'.
"""
import TestGyp
test = TestGyp.TestGyp()
```
Similarly, the footer should be the same in every test:
```
test.pass_test()
```
## Running tests
Test scripts are run by the `gyptest.py` script.
You can specify (an) explicit test script(s) to run:
```
$ python gyptest.py test/hello/gyptest-all.py
PYTHONPATH=/home/knight/src/gyp/trunk/test/lib
TESTGYP_FORMAT=scons
/usr/bin/python test/hello/gyptest-all.py
PASSED
$
```
If you specify a directory, all test scripts
(scripts prefixed with `gyptest-`) underneath
the directory will be run:
```
$ python gyptest.py test/hello
PYTHONPATH=/home/knight/src/gyp/trunk/test/lib
TESTGYP_FORMAT=scons
/usr/bin/python test/hello/gyptest-all.py
PASSED
/usr/bin/python test/hello/gyptest-default.py
PASSED
/usr/bin/python test/hello/gyptest-target.py
PASSED
$
```
Or you can specify the `-a` option to run all scripts
in the tree:
```
$ python gyptest.py -a
PYTHONPATH=/home/knight/src/gyp/trunk/test/lib
TESTGYP_FORMAT=scons
/usr/bin/python test/configurations/gyptest-configurations.py
PASSED
/usr/bin/python test/defines/gyptest-defines.py
PASSED
.
.
.
.
/usr/bin/python test/variables/gyptest-commands.py
PASSED
$
```
If any tests fail during the run,
the `gyptest.py` script will report them in a
summary at the end.
## Debugging tests
Tests that create intermediate output do so under the gyp/out/testworkarea
directory. On test completion, intermediate output is cleaned up. To preserve
this output, set the environment variable PRESERVE=1. This can be handy to
inspect intermediate data when debugging a test.
You can also set PRESERVE\_PASS=1, PRESERVE\_FAIL=1 or PRESERVE\_NO\_RESULT=1
to preserve output for tests that fall into one of those categories.
# Specifying the format (build tool) to use
By default, the `gyptest.py` script will generate configurations for
the "primary" supported build tool for the platform you're on:
Visual Studio on Windows,
Xcode on Mac,
and (currently) SCons on Linux.
An alternate format (build tool) may be specified
using the `-f` option:
```
$ python gyptest.py -f make test/hello/gyptest-all.py
PYTHONPATH=/home/knight/src/gyp/trunk/test/lib
TESTGYP_FORMAT=make
/usr/bin/python test/hello/gyptest-all.py
PASSED
$
```
Multiple tools may be specified in a single pass as
a comma-separated list:
```
$ python gyptest.py -f make,scons test/hello/gyptest-all.py
PYTHONPATH=/home/knight/src/gyp/trunk/test/lib
TESTGYP_FORMAT=make
/usr/bin/python test/hello/gyptest-all.py
PASSED
TESTGYP_FORMAT=scons
/usr/bin/python test/hello/gyptest-all.py
PASSED
$
```
## Test script functions and methods
The `TestGyp` class contains a lot of functionality
intended to make it easy to write tests.
This section describes the most useful pieces for GYP testing.
(The `TestGyp` class is actually a subclass of more generic
`TestCommon` and `TestCmd` base classes
that contain even more functionality than is
described here.)
### Initialization
The standard initialization formula is:
```
import TestGyp
test = TestGyp.TestGyp()
```
This copies the contents of the directory tree in which
the test script lives to a temporary directory for execution,
and arranges for the temporary directory's removal on exit.
By default, any comparisons of output or file contents
must be exact matches for the test to pass.
If you need to use regular expressions for matches,
a useful alternative initialization is:
```
import TestGyp
test = TestGyp.TestGyp(match = TestGyp.match_re,
diff = TestGyp.diff_re)`
```
### Running GYP
The canonical invocation is to simply specify the `.gyp` file to be executed:
```
test.run_gyp('file.gyp')
```
Additional GYP arguments may be specified:
```
test.run_gyp('file.gyp', arguments=['arg1', 'arg2', ...])
```
To execute GYP from a subdirectory (where, presumably, the specified file
lives):
```
test.run_gyp('file.gyp', chdir='subdir')
```
### Running the build tool
Running the build tool requires passing in a `.gyp` file, which may be used to
calculate the name of a specific build configuration file (such as a MSVS
solution file corresponding to the `.gyp` file).
There are several different `.build_*()` methods for invoking different types
of builds.
To invoke a build tool with an explicit `all` target (or equivalent):
```
test.build_all('file.gyp')
```
To invoke a build tool with its default behavior (for example, executing `make`
with no targets specified):
```
test.build_default('file.gyp')
```
To invoke a build tool with an explicit specified target:
```
test.build_target('file.gyp', 'target')
```
### Running executables
The most useful method executes a program built by the GYP-generated
configuration:
```
test.run_built_executable('program')
```
The `.run_built_executable()` method will account for the actual built target
output location for the build tool being tested, as well as tack on any
necessary executable file suffix for the platform (for example `.exe` on
Windows).
`stdout=` and `stderr=` keyword arguments specify expected standard output and
error output, respectively. Failure to match these (if specified) will cause
the test to fail. An explicit `None` value will suppress that verification:
```
test.run_built_executable('program',
stdout="expect this output\n",
stderr=None)
```
Note that the default values are `stdout=None` and `stderr=''` (that is, no
check for standard output, and error output must be empty).
Arbitrary executables (not necessarily those built by GYP) can be executed with
the lower-level `.run()` method:
```
test.run('program')
```
The program must be in the local directory (that is, the temporary directory
for test execution) or be an absolute path name.
### Fetching command output
```
test.stdout()
```
Returns the standard output from the most recent executed command (including
`.run_gyp()`, `.build_*()`, or `.run*()` methods).
```
test.stderr()
```
Returns the error output from the most recent executed command (including
`.run_gyp()`, `.build_*()`, or `.run*()` methods).
### Verifying existence or non-existence of files or directories
```
test.must_exist('file_or_dir')
```
Verifies that the specified file or directory exists, and fails the test if it
doesn't.
```
test.must_not_exist('file_or_dir')
```
Verifies that the specified file or directory does not exist, and fails the
test if it does.
### Verifying file contents
```
test.must_match('file', 'expected content\n')
```
Verifies that the content of the specified file match the expected string, and
fails the test if it does not. By default, the match must be exact, but
line-by-line regular expressions may be used if the `TestGyp` object was
initialized with `TestGyp.match_re`.
```
test.must_not_match('file', 'expected content\n')
```
Verifies that the content of the specified file does _not_ match the expected
string, and fails the test if it does. By default, the match must be exact,
but line-by-line regular expressions may be used if the `TestGyp` object was
initialized with `TestGyp.match_re`.
```
test.must_contain('file', 'substring')
```
Verifies that the specified file contains the specified substring, and fails
the test if it does not.
```
test.must_not_contain('file', 'substring')
```
Verifies that the specified file does not contain the specified substring, and
fails the test if it does.
```
test.must_contain_all_lines(output, lines)
```
Verifies that the output string contains all of the "lines" in the specified
list of lines. In practice, the lines can be any substring and need not be
`\n`-terminated lines per se. If any line is missing, the test fails.
```
test.must_not_contain_any_lines(output, lines)
```
Verifies that the output string does _not_ contain any of the "lines" in the
specified list of lines. In practice, the lines can be any substring and need
not be `\n`-terminated lines per se. If any line exists in the output string,
the test fails.
```
test.must_contain_any_line(output, lines)
```
Verifies that the output string contains at least one of the "lines" in the
specified list of lines. In practice, the lines can be any substring and need
not be `\n`-terminated lines per se. If none of the specified lines is present,
the test fails.
### Reading file contents
```
test.read('file')
```
Returns the contents of the specified file. Directory elements contained in a
list will be joined:
```
test.read(['subdir', 'file'])
```
### Test success or failure
```
test.fail_test()
```
Fails the test, reporting `FAILED` on standard output and exiting with an exit
status of `1`.
```
test.pass_test()
```
Passes the test, reporting `PASSED` on standard output and exiting with an exit
status of `0`.
```
test.no_result()
```
Indicates the test had no valid result (i.e., the conditions could not be
tested because of an external factor like a full file system). Reports `NO
RESULT` on standard output and exits with a status of `2`.

View file

@ -0,0 +1,965 @@
# User Documentation
## Introduction
This document is intended to provide a user-level guide to GYP. The
emphasis here is on how to use GYP to accomplish specific tasks, not on
the complete technical language specification. (For that, see the
[LanguageSpecification](LanguageSpecification.md).)
The document below starts with some overviews to provide context: an
overview of the structure of a `.gyp` file itself, an overview of a
typical executable-program target in a `.gyp` file, an an overview of a
typical library target in a `.gyp` file.
After the overviews, there are examples of `gyp` patterns for different
common use cases.
## Skeleton of a typical Chromium .gyp file
Here is the skeleton of a typical `.gyp` file in the Chromium tree:
```
{
'variables': {
.
.
.
},
'includes': [
'../build/common.gypi',
],
'target_defaults': {
.
.
.
},
'targets': [
{
'target_name': 'target_1',
.
.
.
},
{
'target_name': 'target_2',
.
.
.
},
],
'conditions': [
['OS=="linux"', {
'targets': [
{
'target_name': 'linux_target_3',
.
.
.
},
],
}],
['OS=="win"', {
'targets': [
{
'target_name': 'windows_target_4',
.
.
.
},
],
}, { # OS != "win"
'targets': [
{
'target_name': 'non_windows_target_5',
.
.
.
},
}],
],
}
```
The entire file just contains a Python dictionary. (It's actually JSON,
with two small Pythonic deviations: comments are introduced with `#`,
and a `,` (comma)) is legal after the last element in a list or
dictionary.)
The top-level pieces in the `.gyp` file are as follows:
`'variables'`: Definitions of variables that can be interpolated and
used in various other parts of the file.
`'includes'`: A list of of other files that will be included in this
file. By convention, included files have the suffix `.gypi` (gyp
include).
`'target_defaults'`: Settings that will apply to _all_ of the targets
defined in this `.gyp` file.
`'targets'`: The list of targets for which this `.gyp` file can
generate builds. Each target is a dictionary that contains settings
describing all the information necessary to build the target.
`'conditions'`: A list of condition specifications that can modify the
contents of the items in the global dictionary defined by this `.gyp`
file based on the values of different variables. As implied by the
above example, the most common use of a `conditions` section in the
top-level dictionary is to add platform-specific targets to the
`targets` list.
## Skeleton of a typical executable target in a .gyp file
The most straightforward target is probably a simple executable program.
Here is an example `executable` target that demonstrates the features
that should cover most simple uses of gyp:
```
{
'targets': [
{
'target_name': 'foo',
'type': 'executable',
'msvs_guid': '5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65',
'dependencies': [
'xyzzy',
'../bar/bar.gyp:bar',
],
'defines': [
'DEFINE_FOO',
'DEFINE_A_VALUE=value',
],
'include_dirs': [
'..',
],
'sources': [
'file1.cc',
'file2.cc',
],
'conditions': [
['OS=="linux"', {
'defines': [
'LINUX_DEFINE',
],
'include_dirs': [
'include/linux',
],
}],
['OS=="win"', {
'defines': [
'WINDOWS_SPECIFIC_DEFINE',
],
}, { # OS != "win",
'defines': [
'NON_WINDOWS_DEFINE',
],
}]
],
},
],
}
```
The top-level settings in the target include:
`'target_name'`: The name by which the target should be known, which
should be unique across all `.gyp` files. This name will be used as the
project name in the generated Visual Studio solution, as the target name
in the generated XCode configuration, and as the alias for building this
target from the command line of the generated SCons configuration.
`'type'`: Set to `executable`, logically enough.
`'msvs_guid'`: THIS IS ONLY TRANSITIONAL. This is a hard-coded GUID
values that will be used in the generated Visual Studio solution
file(s). This allows us to check in a `chrome.sln` file that
interoperates with gyp-generated project files. Once everything in
Chromium is being generated by gyp, it will no longer be important that
the GUIDs stay constant across invocations, and we'll likely get rid of
these settings,
`'dependencies'`: This lists other targets that this target depends on.
The gyp-generated files will guarantee that the other targets are built
before this target. Any library targets in the `dependencies` list will
be linked with this target. The various settings (`defines`,
`include_dirs`, etc.) listed in the `direct_dependent_settings` sections
of the targets in this list will be applied to how _this_ target is
built and linked. See the more complete discussion of
`direct_dependent_settings`, below.
`'defines'`: The C preprocessor definitions that will be passed in on
compilation command lines (using `-D` or `/D` options).
`'include_dirs'`: The directories in which included header files live.
These will be passed in on compilation command lines (using `-I` or `/I`
options).
`'sources'`: The source files for this target.
`'conditions'`: A block of conditions that will be evaluated to update
the different settings in the target dictionary.
## Skeleton of a typical library target in a .gyp file
The vast majority of targets are libraries. Here is an example of a
library target including the additional features that should cover most
needs of libraries:
```
{
'targets': [
{
'target_name': 'foo',
'type': '<(library)'
'msvs_guid': '5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65',
'dependencies': [
'xyzzy',
'../bar/bar.gyp:bar',
],
'defines': [
'DEFINE_FOO',
'DEFINE_A_VALUE=value',
],
'include_dirs': [
'..',
],
'direct_dependent_settings': {
'defines': [
'DEFINE_FOO',
'DEFINE_ADDITIONAL',
],
'linkflags': [
],
},
'export_dependent_settings': [
'../bar/bar.gyp:bar',
],
'sources': [
'file1.cc',
'file2.cc',
],
'conditions': [
['OS=="linux"', {
'defines': [
'LINUX_DEFINE',
],
'include_dirs': [
'include/linux',
],
],
['OS=="win"', {
'defines': [
'WINDOWS_SPECIFIC_DEFINE',
],
}, { # OS != "win",
'defines': [
'NON_WINDOWS_DEFINE',
],
}]
],
],
}
```
The possible entries in a library target are largely the same as those
that can be specified for an executable target (`defines`,
`include_dirs`, etc.). The differences include:
`'type'`: This should almost always be set to '<(library)', which allows
the user to define at gyp time whether libraries are to be built static
or shared. (On Linux, at least, linking with shared libraries saves
significant link time.) If it's necessary to pin down the type of
library to be built, the `type` can be set explicitly to
`static_library` or `shared_library`.
`'direct_dependent_settings'`: This defines the settings that will be
applied to other targets that _directly depend_ on this target--that is,
that list _this_ target in their `'dependencies'` setting. This is
where you list the `defines`, `include_dirs`, `cflags` and `linkflags`
that other targets that compile or link against this target need to
build consistently.
`'export_dependent_settings'`: This lists the targets whose
`direct_dependent_settings` should be "passed on" to other targets that
use (depend on) this target. `TODO: expand on this description.`
## Use Cases
These use cases are intended to cover the most common actions performed
by developers using GYP.
Note that these examples are _not_ fully-functioning, self-contained
examples (or else they'd be way too long). Each example mostly contains
just the keywords and settings relevant to the example, with perhaps a
few extra keywords for context. The intent is to try to show the
specific pieces you need to pay attention to when doing something.
[NOTE: if practical use shows that these examples are confusing without
additional context, please add what's necessary to clarify things.]
### Add new source files
There are similar but slightly different patterns for adding a
platform-independent source file vs. adding a source file that only
builds on some of the supported platforms.
#### Add a source file that builds on all platforms
**Simplest possible case**: You are adding a file(s) that builds on all
platforms.
Just add the file(s) to the `sources` list of the appropriate dictionary
in the `targets` list:
```
{
'targets': [
{
'target_name': 'my_target',
'type': 'executable',
'sources': [
'../other/file_1.cc',
'new_file.cc',
'subdir/file3.cc',
],
},
],
},
```
File path names are relative to the directory in which the `.gyp` file lives.
Keep the list sorted alphabetically (unless there's a really, really,
_really_ good reason not to).
#### Add a platform-specific source file
##### Your platform-specific file is named `*_linux.{ext}`, `*_mac.{ext}`, `*_posix.{ext}` or `*_win.{ext}`
The simplest way to add a platform-specific source file, assuming you're
adding a completely new file and get to name it, is to use one of the
following standard suffixes:
* `_linux` (e.g. `foo_linux.cc`)
* `_mac` (e.g. `foo_mac.cc`)
* `_posix` (e.g. `foo_posix.cc`)
* `_win` (e.g. `foo_win.cc`)
Simply add the file to the `sources` list of the appropriate dict within
the `targets` list, like you would any other source file.
```
{
'targets': [
{
'target_name': 'foo',
'type': 'executable',
'sources': [
'independent.cc',
'specific_win.cc',
],
},
],
},
```
The Chromium `.gyp` files all have appropriate `conditions` entries to
filter out the files that aren't appropriate for the current platform.
In the above example, the `specific_win.cc` file will be removed
automatically from the source-list on non-Windows builds.
##### Your platform-specific file does not use an already-defined pattern
If your platform-specific file does not contain a
`*_{linux,mac,posix,win}` substring (or some other pattern that's
already in the `conditions` for the target), and you can't change the
file name, there are two patterns that can be used.
**Preferred**: Add the file to the `sources` list of the appropriate
dictionary within the `targets` list. Add an appropriate `conditions`
section to exclude the specific files name:
```
{
'targets': [
{
'target_name': 'foo',
'type': 'executable',
'sources': [
'linux_specific.cc',
],
'conditions': [
['OS != "linux"', {
'sources!': [
# Linux-only; exclude on other platforms.
'linux_specific.cc',
]
}[,
],
},
],
},
```
Despite the duplicate listing, the above is generally preferred because
the `sources` list contains a useful global list of all sources on all
platforms with consistent sorting on all platforms.
**Non-preferred**: In some situations, however, it might make sense to
list a platform-specific file only in a `conditions` section that
specifically _includes_ it in the `sources` list:
```
{
'targets': [
{
'target_name': 'foo',
'type': 'executable',
'sources': [],
['OS == "linux"', {
'sources': [
# Only add to sources list on Linux.
'linux_specific.cc',
]
}],
},
],
},
```
The above two examples end up generating equivalent builds, with the
small exception that the `sources` lists will list the files in
different orders. (The first example defines explicitly where
`linux_specific.cc` appears in the list--perhaps in in the
middle--whereas the second example will always tack it on to the end of
the list.)
**Including or excluding files using patterns**: There are more
complicated ways to construct a `sources` list based on patterns. See
`TODO` below.
### Add a new executable
An executable program is probably the most straightforward type of
target, since all it typically needs is a list of source files, some
compiler/linker settings (probably varied by platform), and some library
targets on which it depends and which must be used in the final link.
#### Add an executable that builds on all platforms
Add a dictionary defining the new executable target to the `targets`
list in the appropriate `.gyp` file. Example:
```
{
'targets': [
{
'target_name': 'new_unit_tests',
'type': 'executable',
'defines': [
'FOO',
],
'include_dirs': [
'..',
],
'dependencies': [
'other_target_in_this_file',
'other_gyp2:target_in_other_gyp2',
],
'sources': [
'new_additional_source.cc',
'new_unit_tests.cc',
],
},
],
}
```
#### Add a platform-specific executable
Add a dictionary defining the new executable target to the `targets`
list within an appropriate `conditions` block for the platform. The
`conditions` block should be a sibling to the top-level `targets` list:
```
{
'targets': [
],
'conditions': [
['OS=="win"', {
'targets': [
{
'target_name': 'new_unit_tests',
'type': 'executable',
'defines': [
'FOO',
],
'include_dirs': [
'..',
],
'dependencies': [
'other_target_in_this_file',
'other_gyp2:target_in_other_gyp2',
],
'sources': [
'new_additional_source.cc',
'new_unit_tests.cc',
],
},
],
}],
],
}
```
### Add settings to a target
There are several different types of settings that can be defined for
any given target.
#### Add new preprocessor definitions (`-D` or `/D` flags)
New preprocessor definitions are added by the `defines` setting:
```
{
'targets': [
{
'target_name': 'existing_target',
'defines': [
'FOO',
'BAR=some_value',
],
},
],
},
```
These may be specified directly in a target's settings, as in the above
example, or in a `conditions` section.
#### Add a new include directory (`-I` or `/I` flags)
New include directories are added by the `include_dirs` setting:
```
{
'targets': [
{
'target_name': 'existing_target',
'include_dirs': [
'..',
'include',
],
},
],
},
```
These may be specified directly in a target's settings, as in the above
example, or in a `conditions` section.
#### Add new compiler flags
Specific compiler flags can be added with the `cflags` setting:
```
{
'targets': [
{
'target_name': 'existing_target',
'conditions': [
['OS=="win"', {
'cflags': [
'/WX',
],
}, { # OS != "win"
'cflags': [
'-Werror',
],
}],
],
},
],
},
```
Because these flags will be specific to the actual compiler involved,
they will almost always be only set within a `conditions` section.
#### Add new linker flags
Setting linker flags is OS-specific. On linux and most non-mac posix
systems, they can be added with the `ldflags` setting:
```
{
'targets': [
{
'target_name': 'existing_target',
'conditions': [
['OS=="linux"', {
'ldflags': [
'-pthread',
],
}],
],
},
],
},
```
Because these flags will be specific to the actual linker involved,
they will almost always be only set within a `conditions` section.
On OS X, linker settings are set via `xcode_settings`, on Windows via
`msvs_settings`.
#### Exclude settings on a platform
Any given settings keyword (`defines`, `include_dirs`, etc.) has a
corresponding form with a trailing `!` (exclamation point) to remove
values from a setting. One useful example of this is to remove the
Linux `-Werror` flag from the global settings defined in
`build/common.gypi`:
```
{
'targets': [
{
'target_name': 'third_party_target',
'conditions': [
['OS=="linux"', {
'cflags!': [
'-Werror',
],
}],
],
},
],
},
```
### Cross-compiling
GYP has some (relatively limited) support for cross-compiling.
If the variable `GYP_CROSSCOMPILE` or one of the toolchain-related
variables (like `CC_host` or `CC_target`) is set, GYP will think that
you wish to do a cross-compile.
When cross-compiling, each target can be part of a "host" build, a
"target" build, or both. By default, the target is assumed to be (only)
part of the "target" build. The 'toolsets' property can be set on a
target to change the default.
A target's dependencies are assumed to match the build type (so, if A
depends on B, by default that means that a target build of A depends on
a target build of B). You can explicitly depend on targets across
toolchains by specifying "#host" or "#target" in the dependencies list.
If GYP is not doing a cross-compile, the "#host" and "#target" will be
stripped as needed, so nothing breaks.
### Add a new library
TODO: write intro
#### Add a library that builds on all platforms
Add the a dictionary defining the new library target to the `targets`
list in the appropriate `.gyp` file. Example:
```
{
'targets': [
{
'target_name': 'new_library',
'type': '<(library)',
'defines': [
'FOO',
'BAR=some_value',
],
'include_dirs': [
'..',
],
'dependencies': [
'other_target_in_this_file',
'other_gyp2:target_in_other_gyp2',
],
'direct_dependent_settings': {
'include_dirs': '.',
},
'export_dependent_settings': [
'other_target_in_this_file',
],
'sources': [
'new_additional_source.cc',
'new_library.cc',
],
},
],
}
```
The use of the `<(library)` variable above should be the default `type`
setting for most library targets, as it allows the developer to choose,
at `gyp` time, whether to build with static or shared libraries.
(Building with shared libraries saves a _lot_ of link time on Linux.)
It may be necessary to build a specific library as a fixed type. Is so,
the `type` field can be hard-wired appropriately. For a static library:
```
'type': 'static_library',
```
For a shared library:
```
'type': 'shared_library',
```
#### Add a platform-specific library
Add a dictionary defining the new library target to the `targets` list
within a `conditions` block that's a sibling to the top-level `targets`
list:
```
{
'targets': [
],
'conditions': [
['OS=="win"', {
'targets': [
{
'target_name': 'new_library',
'type': '<(library)',
'defines': [
'FOO',
'BAR=some_value',
],
'include_dirs': [
'..',
],
'dependencies': [
'other_target_in_this_file',
'other_gyp2:target_in_other_gyp2',
],
'direct_dependent_settings': {
'include_dirs': '.',
},
'export_dependent_settings': [
'other_target_in_this_file',
],
'sources': [
'new_additional_source.cc',
'new_library.cc',
],
},
],
}],
],
}
```
### Dependencies between targets
GYP provides useful primitives for establishing dependencies between
targets, which need to be configured in the following situations.
#### Linking with another library target
```
{
'targets': [
{
'target_name': 'foo',
'dependencies': [
'libbar',
],
},
{
'target_name': 'libbar',
'type': '<(library)',
'sources': [
],
},
],
}
```
Note that if the library target is in a different `.gyp` file, you have
to specify the path to other `.gyp` file, relative to this `.gyp` file's
directory:
```
{
'targets': [
{
'target_name': 'foo',
'dependencies': [
'../bar/bar.gyp:libbar',
],
},
],
}
```
Adding a library often involves updating multiple `.gyp` files, adding
the target to the appropriate `.gyp` file (possibly a newly-added `.gyp`
file), and updating targets in the other `.gyp` files that depend on
(link with) the new library.
#### Compiling with necessary flags for a library target dependency
We need to build a library (often a third-party library) with specific
preprocessor definitions or command-line flags, and need to ensure that
targets that depend on the library build with the same settings. This
situation is handled by a `direct_dependent_settings` block:
```
{
'targets': [
{
'target_name': 'foo',
'type': 'executable',
'dependencies': [
'libbar',
],
},
{
'target_name': 'libbar',
'type': '<(library)',
'defines': [
'LOCAL_DEFINE_FOR_LIBBAR',
'DEFINE_TO_USE_LIBBAR',
],
'include_dirs': [
'..',
'include/libbar',
],
'direct_dependent_settings': {
'defines': [
'DEFINE_TO_USE_LIBBAR',
],
'include_dirs': [
'include/libbar',
],
},
},
],
}
```
In the above example, the sources of the `foo` executable will be
compiled with the options `-DDEFINE_TO_USE_LIBBAR -Iinclude/libbar`,
because of those settings' being listed in the
`direct_dependent_settings` block.
Note that these settings will likely need to be replicated in the
settings for the library target itself, so that the library will build
with the same options. This does not prevent the target from defining
additional options for its "internal" use when compiling its own source
files. (In the above example, these are the `LOCAL_DEFINE_FOR_LIBBAR`
define, and the `..` entry in the `include_dirs` list.)
#### When a library depends on an additional library at final link time
```
{
'targets': [
{
'target_name': 'foo',
'type': 'executable',
'dependencies': [
'libbar',
],
},
{
'target_name': 'libbar',
'type': '<(library)',
'dependencies': [
'libother'
],
'export_dependent_settings': [
'libother'
],
},
{
'target_name': 'libother',
'type': '<(library)',
'direct_dependent_settings': {
'defines': [
'DEFINE_FOR_LIBOTHER',
],
'include_dirs': [
'include/libother',
],
},
},
],
}
```
### Support for Mac OS X bundles
gyp supports building bundles on OS X (.app, .framework, .bundle, etc).
Here is an example of this:
```
{
'target_name': 'test_app',
'product_name': 'Test App Gyp',
'type': 'executable',
'mac_bundle': 1,
'sources': [
'main.m',
'TestAppAppDelegate.h',
'TestAppAppDelegate.m',
],
'mac_bundle_resources': [
'TestApp/English.lproj/InfoPlist.strings',
'TestApp/English.lproj/MainMenu.xib',
],
'link_settings': {
'libraries': [
'$(SDKROOT)/System/Library/Frameworks/Cocoa.framework',
],
},
'xcode_settings': {
'INFOPLIST_FILE': 'TestApp/TestApp-Info.plist',
},
},
```
The `mac_bundle` key tells gyp that this target should be a bundle.
`executable` targets get extension `.app` by default, `shared_library`
targets get `.framework` but you can change the bundle extensions by
setting `product_extension` if you want. Files listed in
`mac_bundle_resources` will be copied to the bundle's `Resource` folder
of the bundle. You can also set
`process_outputs_as_mac_bundle_resources` to 1 in actions and rules to
let the output of actions and rules be added to that folder (similar to
`process_outputs_as_sources`). If `product_name` is not set, the bundle
will be named after `target_name`as usual.
### Move files (refactoring)
TODO
### Custom build steps
TODO
#### Adding an explicit build step to generate specific files
TODO
#### Adding a rule to handle files with a new suffix
TODO
### Build flavors
TODO

View file

@ -5,8 +5,8 @@
# found in the LICENSE file.
import os
import sys
import subprocess
import sys
def IsCygwin():

View file

@ -32,20 +32,20 @@ ENTRY_TYPE_GUIDS = {
def MakeGuid(name, seed="msvs_new"):
"""Returns a GUID for the specified target name.
Args:
name: Target name.
seed: Seed for MD5 hash.
Returns:
A GUID-line string calculated from the name and seed.
Args:
name: Target name.
seed: Seed for SHA-256 hash.
Returns:
A GUID-line string calculated from the name and seed.
This generates something which looks like a GUID, but depends only on the
name and seed. This means the same name/seed will always generate the same
GUID, so that projects and solutions which refer to each other can explicitly
determine the GUID to refer to explicitly. It also means that the GUID will
not change when the project for a target is rebuilt.
"""
# Calculate a MD5 signature for the seed and name.
d = hashlib.md5((str(seed) + str(name)).encode("utf-8")).hexdigest().upper()
This generates something which looks like a GUID, but depends only on the
name and seed. This means the same name/seed will always generate the same
GUID, so that projects and solutions which refer to each other can explicitly
determine the GUID to refer to explicitly. It also means that the GUID will
not change when the project for a target is rebuilt.
"""
# Calculate a SHA-256 signature for the seed and name.
d = hashlib.sha256((str(seed) + str(name)).encode("utf-8")).hexdigest().upper()
# Convert most of the signature to GUID form (discard the rest)
guid = (
"{"
@ -78,15 +78,15 @@ class MSVSFolder(MSVSSolutionEntry):
def __init__(self, path, name=None, entries=None, guid=None, items=None):
"""Initializes the folder.
Args:
path: Full path to the folder.
name: Name of the folder.
entries: List of folder entries to nest inside this folder. May contain
Folder or Project objects. May be None, if the folder is empty.
guid: GUID to use for folder, if not None.
items: List of solution items to include in the folder project. May be
None, if the folder does not directly contain items.
"""
Args:
path: Full path to the folder.
name: Name of the folder.
entries: List of folder entries to nest inside this folder. May contain
Folder or Project objects. May be None, if the folder is empty.
guid: GUID to use for folder, if not None.
items: List of solution items to include in the folder project. May be
None, if the folder does not directly contain items.
"""
if name:
self.name = name
else:
@ -128,19 +128,19 @@ class MSVSProject(MSVSSolutionEntry):
):
"""Initializes the project.
Args:
path: Absolute path to the project file.
name: Name of project. If None, the name will be the same as the base
name of the project file.
dependencies: List of other Project objects this project is dependent
upon, if not None.
guid: GUID to use for project, if not None.
spec: Dictionary specifying how to build this project.
build_file: Filename of the .gyp file that the vcproj file comes from.
config_platform_overrides: optional dict of configuration platforms to
used in place of the default for this target.
fixpath_prefix: the path used to adjust the behavior of _fixpath
"""
Args:
path: Absolute path to the project file.
name: Name of project. If None, the name will be the same as the base
name of the project file.
dependencies: List of other Project objects this project is dependent
upon, if not None.
guid: GUID to use for project, if not None.
spec: Dictionary specifying how to build this project.
build_file: Filename of the .gyp file that the vcproj file comes from.
config_platform_overrides: optional dict of configuration platforms to
used in place of the default for this target.
fixpath_prefix: the path used to adjust the behavior of _fixpath
"""
self.path = path
self.guid = guid
self.spec = spec
@ -195,16 +195,16 @@ class MSVSSolution:
):
"""Initializes the solution.
Args:
path: Path to solution file.
version: Format version to emit.
entries: List of entries in solution. May contain Folder or Project
objects. May be None, if the folder is empty.
variants: List of build variant strings. If none, a default list will
be used.
websiteProperties: Flag to decide if the website properties section
is generated.
"""
Args:
path: Path to solution file.
version: Format version to emit.
entries: List of entries in solution. May contain Folder or Project
objects. May be None, if the folder is empty.
variants: List of build variant strings. If none, a default list will
be used.
websiteProperties: Flag to decide if the website properties section
is generated.
"""
self.path = path
self.websiteProperties = websiteProperties
self.version = version
@ -230,9 +230,9 @@ class MSVSSolution:
def Write(self, writer=gyp.common.WriteOnDiff):
"""Writes the solution file to disk.
Raises:
IndexError: An entry appears multiple times.
"""
Raises:
IndexError: An entry appears multiple times.
"""
# Walk the entry tree and collect all the folders and projects.
all_entries = set()
entries_to_check = self.entries[:]
@ -285,19 +285,17 @@ class MSVSSolution:
"\tEndProjectSection\r\n"
)
if isinstance(e, MSVSFolder):
if e.items:
f.write("\tProjectSection(SolutionItems) = preProject\r\n")
for i in e.items:
f.write(f"\t\t{i} = {i}\r\n")
f.write("\tEndProjectSection\r\n")
if isinstance(e, MSVSFolder) and e.items:
f.write("\tProjectSection(SolutionItems) = preProject\r\n")
for i in e.items:
f.write(f"\t\t{i} = {i}\r\n")
f.write("\tEndProjectSection\r\n")
if isinstance(e, MSVSProject):
if e.dependencies:
f.write("\tProjectSection(ProjectDependencies) = postProject\r\n")
for d in e.dependencies:
f.write(f"\t\t{d.get_guid()} = {d.get_guid()}\r\n")
f.write("\tEndProjectSection\r\n")
if isinstance(e, MSVSProject) and e.dependencies:
f.write("\tProjectSection(ProjectDependencies) = postProject\r\n")
for d in e.dependencies:
f.write(f"\t\t{d.get_guid()} = {d.get_guid()}\r\n")
f.write("\tEndProjectSection\r\n")
f.write("EndProject\r\n")
@ -353,7 +351,7 @@ class MSVSSolution:
# Folder mappings
# Omit this section if there are no folders
if any([e.entries for e in all_entries if isinstance(e, MSVSFolder)]):
if any(e.entries for e in all_entries if isinstance(e, MSVSFolder)):
f.write("\tGlobalSection(NestedProjects) = preSolution\r\n")
for e in all_entries:
if not isinstance(e, MSVSFolder):

View file

@ -4,7 +4,7 @@
"""Visual Studio project reader/writer."""
import gyp.easy_xml as easy_xml
from gyp import easy_xml
# ------------------------------------------------------------------------------
@ -15,19 +15,19 @@ class Tool:
def __init__(self, name, attrs=None):
"""Initializes the tool.
Args:
name: Tool name.
attrs: Dict of tool attributes; may be None.
"""
Args:
name: Tool name.
attrs: Dict of tool attributes; may be None.
"""
self._attrs = attrs or {}
self._attrs["Name"] = name
def _GetSpecification(self):
"""Creates an element for the tool.
Returns:
A new xml.dom.Element for the tool.
"""
Returns:
A new xml.dom.Element for the tool.
"""
return ["Tool", self._attrs]
@ -37,10 +37,10 @@ class Filter:
def __init__(self, name, contents=None):
"""Initializes the folder.
Args:
name: Filter (folder) name.
contents: List of filenames and/or Filter objects contained.
"""
Args:
name: Filter (folder) name.
contents: List of filenames and/or Filter objects contained.
"""
self.name = name
self.contents = list(contents or [])
@ -54,13 +54,13 @@ class Writer:
def __init__(self, project_path, version, name, guid=None, platforms=None):
"""Initializes the project.
Args:
project_path: Path to the project file.
version: Format version to emit.
name: Name of the project.
guid: GUID to use for project, if not None.
platforms: Array of string, the supported platforms. If null, ['Win32']
"""
Args:
project_path: Path to the project file.
version: Format version to emit.
name: Name of the project.
guid: GUID to use for project, if not None.
platforms: Array of string, the supported platforms. If null, ['Win32']
"""
self.project_path = project_path
self.version = version
self.name = name
@ -79,26 +79,26 @@ class Writer:
self.files_section = ["Files"]
# Keep a dict keyed on filename to speed up access.
self.files_dict = dict()
self.files_dict = {}
def AddToolFile(self, path):
"""Adds a tool file to the project.
Args:
path: Relative path from project to tool file.
"""
Args:
path: Relative path from project to tool file.
"""
self.tool_files_section.append(["ToolFile", {"RelativePath": path}])
def _GetSpecForConfiguration(self, config_type, config_name, attrs, tools):
"""Returns the specification for a configuration.
Args:
config_type: Type of configuration node.
config_name: Configuration name.
attrs: Dict of configuration attributes; may be None.
tools: List of tools (strings or Tool objects); may be None.
Returns:
"""
Args:
config_type: Type of configuration node.
config_name: Configuration name.
attrs: Dict of configuration attributes; may be None.
tools: List of tools (strings or Tool objects); may be None.
Returns:
"""
# Handle defaults
if not attrs:
attrs = {}
@ -122,23 +122,23 @@ class Writer:
def AddConfig(self, name, attrs=None, tools=None):
"""Adds a configuration to the project.
Args:
name: Configuration name.
attrs: Dict of configuration attributes; may be None.
tools: List of tools (strings or Tool objects); may be None.
"""
Args:
name: Configuration name.
attrs: Dict of configuration attributes; may be None.
tools: List of tools (strings or Tool objects); may be None.
"""
spec = self._GetSpecForConfiguration("Configuration", name, attrs, tools)
self.configurations_section.append(spec)
def _AddFilesToNode(self, parent, files):
"""Adds files and/or filters to the parent node.
Args:
parent: Destination node
files: A list of Filter objects and/or relative paths to files.
Args:
parent: Destination node
files: A list of Filter objects and/or relative paths to files.
Will call itself recursively, if the files list contains Filter objects.
"""
Will call itself recursively, if the files list contains Filter objects.
"""
for f in files:
if isinstance(f, Filter):
node = ["Filter", {"Name": f.name}]
@ -151,13 +151,13 @@ class Writer:
def AddFiles(self, files):
"""Adds files to the project.
Args:
files: A list of Filter objects and/or relative paths to files.
Args:
files: A list of Filter objects and/or relative paths to files.
This makes a copy of the file/filter tree at the time of this call. If you
later add files to a Filter object which was passed into a previous call
to AddFiles(), it will not be reflected in this project.
"""
This makes a copy of the file/filter tree at the time of this call. If you
later add files to a Filter object which was passed into a previous call
to AddFiles(), it will not be reflected in this project.
"""
self._AddFilesToNode(self.files_section, files)
# TODO(rspangler) This also doesn't handle adding files to an existing
# filter. That is, it doesn't merge the trees.
@ -165,15 +165,15 @@ class Writer:
def AddFileConfig(self, path, config, attrs=None, tools=None):
"""Adds a configuration to a file.
Args:
path: Relative path to the file.
config: Name of configuration to add.
attrs: Dict of configuration attributes; may be None.
tools: List of tools (strings or Tool objects); may be None.
Args:
path: Relative path to the file.
config: Name of configuration to add.
attrs: Dict of configuration attributes; may be None.
tools: List of tools (strings or Tool objects); may be None.
Raises:
ValueError: Relative path does not match any file added via AddFiles().
"""
Raises:
ValueError: Relative path does not match any file added via AddFiles().
"""
# Find the file node with the right relative path
parent = self.files_dict.get(path)
if not parent:

View file

@ -35,10 +35,10 @@ _msbuild_name_of_tool = {}
class _Tool:
"""Represents a tool used by MSVS or MSBuild.
Attributes:
msvs_name: The name of the tool in MSVS.
msbuild_name: The name of the tool in MSBuild.
"""
Attributes:
msvs_name: The name of the tool in MSVS.
msbuild_name: The name of the tool in MSBuild.
"""
def __init__(self, msvs_name, msbuild_name):
self.msvs_name = msvs_name
@ -48,11 +48,11 @@ class _Tool:
def _AddTool(tool):
"""Adds a tool to the four dictionaries used to process settings.
This only defines the tool. Each setting also needs to be added.
This only defines the tool. Each setting also needs to be added.
Args:
tool: The _Tool object to be added.
"""
Args:
tool: The _Tool object to be added.
"""
_msvs_validators[tool.msvs_name] = {}
_msbuild_validators[tool.msbuild_name] = {}
_msvs_to_msbuild_converters[tool.msvs_name] = {}
@ -70,35 +70,35 @@ class _Type:
def ValidateMSVS(self, value):
"""Verifies that the value is legal for MSVS.
Args:
value: the value to check for this type.
Args:
value: the value to check for this type.
Raises:
ValueError if value is not valid for MSVS.
"""
Raises:
ValueError if value is not valid for MSVS.
"""
def ValidateMSBuild(self, value):
"""Verifies that the value is legal for MSBuild.
Args:
value: the value to check for this type.
Args:
value: the value to check for this type.
Raises:
ValueError if value is not valid for MSBuild.
"""
Raises:
ValueError if value is not valid for MSBuild.
"""
def ConvertToMSBuild(self, value):
"""Returns the MSBuild equivalent of the MSVS value given.
Args:
value: the MSVS value to convert.
Args:
value: the MSVS value to convert.
Returns:
the MSBuild equivalent.
Returns:
the MSBuild equivalent.
Raises:
ValueError if value is not valid.
"""
Raises:
ValueError if value is not valid.
"""
return value
@ -141,7 +141,7 @@ class _Boolean(_Type):
"""Boolean settings, can have the values 'false' or 'true'."""
def _Validate(self, value):
if value != "true" and value != "false":
if value not in {"true", "false"}:
raise ValueError("expected bool; got %r" % value)
def ValidateMSVS(self, value):
@ -171,22 +171,22 @@ class _Integer(_Type):
int(value, self._msbuild_base)
def ConvertToMSBuild(self, value):
msbuild_format = (self._msbuild_base == 10) and "%d" or "0x%04x"
msbuild_format = ((self._msbuild_base == 10) and "%d") or "0x%04x"
return msbuild_format % int(value)
class _Enumeration(_Type):
"""Type of settings that is an enumeration.
In MSVS, the values are indexes like '0', '1', and '2'.
MSBuild uses text labels that are more representative, like 'Win32'.
In MSVS, the values are indexes like '0', '1', and '2'.
MSBuild uses text labels that are more representative, like 'Win32'.
Constructor args:
label_list: an array of MSBuild labels that correspond to the MSVS index.
In the rare cases where MSVS has skipped an index value, None is
used in the array to indicate the unused spot.
new: an array of labels that are new to MSBuild.
"""
Constructor args:
label_list: an array of MSBuild labels that correspond to the MSVS index.
In the rare cases where MSVS has skipped an index value, None is
used in the array to indicate the unused spot.
new: an array of labels that are new to MSBuild.
"""
def __init__(self, label_list, new=None):
_Type.__init__(self)
@ -234,23 +234,23 @@ _newly_boolean = _Enumeration(["", "false", "true"])
def _Same(tool, name, setting_type):
"""Defines a setting that has the same name in MSVS and MSBuild.
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
name: the name of the setting.
setting_type: the type of this setting.
"""
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
name: the name of the setting.
setting_type: the type of this setting.
"""
_Renamed(tool, name, name, setting_type)
def _Renamed(tool, msvs_name, msbuild_name, setting_type):
"""Defines a setting for which the name has changed.
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
msvs_name: the name of the MSVS setting.
msbuild_name: the name of the MSBuild setting.
setting_type: the type of this setting.
"""
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
msvs_name: the name of the MSVS setting.
msbuild_name: the name of the MSBuild setting.
setting_type: the type of this setting.
"""
def _Translate(value, msbuild_settings):
msbuild_tool_settings = _GetMSBuildToolSettings(msbuild_settings, tool)
@ -272,13 +272,13 @@ def _MovedAndRenamed(
):
"""Defines a setting that may have moved to a new section.
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
msvs_settings_name: the MSVS name of the setting.
msbuild_tool_name: the name of the MSBuild tool to place the setting under.
msbuild_settings_name: the MSBuild name of the setting.
setting_type: the type of this setting.
"""
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
msvs_settings_name: the MSVS name of the setting.
msbuild_tool_name: the name of the MSBuild tool to place the setting under.
msbuild_settings_name: the MSBuild name of the setting.
setting_type: the type of this setting.
"""
def _Translate(value, msbuild_settings):
tool_settings = msbuild_settings.setdefault(msbuild_tool_name, {})
@ -293,11 +293,11 @@ def _MovedAndRenamed(
def _MSVSOnly(tool, name, setting_type):
"""Defines a setting that is only found in MSVS.
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
name: the name of the setting.
setting_type: the type of this setting.
"""
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
name: the name of the setting.
setting_type: the type of this setting.
"""
def _Translate(unused_value, unused_msbuild_settings):
# Since this is for MSVS only settings, no translation will happen.
@ -310,11 +310,11 @@ def _MSVSOnly(tool, name, setting_type):
def _MSBuildOnly(tool, name, setting_type):
"""Defines a setting that is only found in MSBuild.
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
name: the name of the setting.
setting_type: the type of this setting.
"""
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
name: the name of the setting.
setting_type: the type of this setting.
"""
def _Translate(value, msbuild_settings):
# Let msbuild-only properties get translated as-is from msvs_settings.
@ -328,11 +328,11 @@ def _MSBuildOnly(tool, name, setting_type):
def _ConvertedToAdditionalOption(tool, msvs_name, flag):
"""Defines a setting that's handled via a command line option in MSBuild.
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
msvs_name: the name of the MSVS setting that if 'true' becomes a flag
flag: the flag to insert at the end of the AdditionalOptions
"""
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
msvs_name: the name of the MSVS setting that if 'true' becomes a flag
flag: the flag to insert at the end of the AdditionalOptions
"""
def _Translate(value, msbuild_settings):
if value == "true":
@ -384,20 +384,19 @@ _EXCLUDED_SUFFIX_RE = re.compile("^(.*)_excluded$")
def _ValidateExclusionSetting(setting, settings, error_msg, stderr=sys.stderr):
"""Verify that 'setting' is valid if it is generated from an exclusion list.
If the setting appears to be generated from an exclusion list, the root name
is checked.
If the setting appears to be generated from an exclusion list, the root name
is checked.
Args:
setting: A string that is the setting name to validate
settings: A dictionary where the keys are valid settings
error_msg: The message to emit in the event of error
stderr: The stream receiving the error messages.
"""
Args:
setting: A string that is the setting name to validate
settings: A dictionary where the keys are valid settings
error_msg: The message to emit in the event of error
stderr: The stream receiving the error messages.
"""
# This may be unrecognized because it's an exclusion list. If the
# setting name has the _excluded suffix, then check the root name.
unrecognized = True
m = re.match(_EXCLUDED_SUFFIX_RE, setting)
if m:
if m := re.match(_EXCLUDED_SUFFIX_RE, setting):
root_setting = m.group(1)
unrecognized = root_setting not in settings
@ -409,11 +408,11 @@ def _ValidateExclusionSetting(setting, settings, error_msg, stderr=sys.stderr):
def FixVCMacroSlashes(s):
"""Replace macros which have excessive following slashes.
These macros are known to have a built-in trailing slash. Furthermore, many
scripts hiccup on processing paths with extra slashes in the middle.
These macros are known to have a built-in trailing slash. Furthermore, many
scripts hiccup on processing paths with extra slashes in the middle.
This list is probably not exhaustive. Add as needed.
"""
This list is probably not exhaustive. Add as needed.
"""
if "$" in s:
s = fix_vc_macro_slashes_regex.sub(r"\1", s)
return s
@ -422,8 +421,8 @@ def FixVCMacroSlashes(s):
def ConvertVCMacrosToMSBuild(s):
"""Convert the MSVS macros found in the string to the MSBuild equivalent.
This list is probably not exhaustive. Add as needed.
"""
This list is probably not exhaustive. Add as needed.
"""
if "$" in s:
replace_map = {
"$(ConfigurationName)": "$(Configuration)",
@ -445,16 +444,16 @@ def ConvertVCMacrosToMSBuild(s):
def ConvertToMSBuildSettings(msvs_settings, stderr=sys.stderr):
"""Converts MSVS settings (VS2008 and earlier) to MSBuild settings (VS2010+).
Args:
msvs_settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
Args:
msvs_settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
Returns:
A dictionary of MSBuild settings. The key is either the MSBuild tool name
or the empty string (for the global settings). The values are themselves
dictionaries of settings and their values.
"""
Returns:
A dictionary of MSBuild settings. The key is either the MSBuild tool name
or the empty string (for the global settings). The values are themselves
dictionaries of settings and their values.
"""
msbuild_settings = {}
for msvs_tool_name, msvs_tool_settings in msvs_settings.items():
if msvs_tool_name in _msvs_to_msbuild_converters:
@ -493,36 +492,36 @@ def ConvertToMSBuildSettings(msvs_settings, stderr=sys.stderr):
def ValidateMSVSSettings(settings, stderr=sys.stderr):
"""Validates that the names of the settings are valid for MSVS.
Args:
settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
"""
Args:
settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
"""
_ValidateSettings(_msvs_validators, settings, stderr)
def ValidateMSBuildSettings(settings, stderr=sys.stderr):
"""Validates that the names of the settings are valid for MSBuild.
Args:
settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
"""
Args:
settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
"""
_ValidateSettings(_msbuild_validators, settings, stderr)
def _ValidateSettings(validators, settings, stderr):
"""Validates that the settings are valid for MSBuild or MSVS.
We currently only validate the names of the settings, not their values.
We currently only validate the names of the settings, not their values.
Args:
validators: A dictionary of tools and their validators.
settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
"""
Args:
validators: A dictionary of tools and their validators.
settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
"""
for tool_name in settings:
if tool_name in validators:
tool_validators = validators[tool_name]
@ -638,7 +637,9 @@ _Same(
),
) # /RTC1
_Same(
_compile, "BrowseInformation", _Enumeration(["false", "true", "true"]) # /FR
_compile,
"BrowseInformation",
_Enumeration(["false", "true", "true"]), # /FR
) # /Fr
_Same(
_compile,
@ -696,7 +697,9 @@ _Same(
_Enumeration(["false", "Sync", "Async"], new=["SyncCThrow"]), # /EHsc # /EHa
) # /EHs
_Same(
_compile, "FavorSizeOrSpeed", _Enumeration(["Neither", "Speed", "Size"]) # /Ot
_compile,
"FavorSizeOrSpeed",
_Enumeration(["Neither", "Speed", "Size"]), # /Ot
) # /Os
_Same(
_compile,
@ -793,6 +796,8 @@ _MSBuildOnly(
_compile, "CompileAsManaged", _Enumeration([], new=["false", "true"])
) # /clr
_MSBuildOnly(_compile, "CreateHotpatchableImage", _boolean) # /hotpatch
_MSBuildOnly(_compile, "LanguageStandard", _string)
_MSBuildOnly(_compile, "LanguageStandard_C", _string)
_MSBuildOnly(_compile, "MultiProcessorCompilation", _boolean) # /MP
_MSBuildOnly(_compile, "PreprocessOutputPath", _string) # /Fi
_MSBuildOnly(_compile, "ProcessorNumber", _integer) # the number of processors
@ -907,7 +912,9 @@ _target_machine_enumeration = _Enumeration(
) # /MACHINE:X64
_Same(
_link, "AssemblyDebug", _Enumeration(["", "true", "false"]) # /ASSEMBLYDEBUG
_link,
"AssemblyDebug",
_Enumeration(["", "true", "false"]), # /ASSEMBLYDEBUG
) # /ASSEMBLYDEBUG:DISABLE
_Same(
_link,
@ -1157,17 +1164,23 @@ _Renamed(_midl, "ValidateParameters", "ValidateAllParameters", _boolean) # /rob
_MSBuildOnly(_midl, "ApplicationConfigurationMode", _boolean) # /app_config
_MSBuildOnly(_midl, "ClientStubFile", _file_name) # /cstub
_MSBuildOnly(
_midl, "GenerateClientFiles", _Enumeration([], new=["Stub", "None"]) # /client stub
_midl,
"GenerateClientFiles",
_Enumeration([], new=["Stub", "None"]), # /client stub
) # /client none
_MSBuildOnly(
_midl, "GenerateServerFiles", _Enumeration([], new=["Stub", "None"]) # /client stub
_midl,
"GenerateServerFiles",
_Enumeration([], new=["Stub", "None"]), # /client stub
) # /client none
_MSBuildOnly(_midl, "LocaleID", _integer) # /lcid DECIMAL
_MSBuildOnly(_midl, "ServerStubFile", _file_name) # /sstub
_MSBuildOnly(_midl, "SuppressCompilerWarnings", _boolean) # /no_warn
_MSBuildOnly(_midl, "TrackerLogDirectory", _folder_name)
_MSBuildOnly(
_midl, "TypeLibFormat", _Enumeration([], new=["NewFormat", "OldFormat"]) # /newtlb
_midl,
"TypeLibFormat",
_Enumeration([], new=["NewFormat", "OldFormat"]), # /newtlb
) # /oldtlb

View file

@ -7,10 +7,10 @@
"""Unit tests for the MSVSSettings.py file."""
import unittest
import gyp.MSVSSettings as MSVSSettings
from io import StringIO
from gyp import MSVSSettings
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
@ -1143,47 +1143,47 @@ class TestSequenceFunctions(unittest.TestCase):
def testConvertToMSBuildSettings_actual(self):
"""Tests the conversion of an actual project.
A VS2008 project with most of the options defined was created through the
VS2008 IDE. It was then converted to VS2010. The tool settings found in
the .vcproj and .vcxproj files were converted to the two dictionaries
msvs_settings and expected_msbuild_settings.
A VS2008 project with most of the options defined was created through the
VS2008 IDE. It was then converted to VS2010. The tool settings found in
the .vcproj and .vcxproj files were converted to the two dictionaries
msvs_settings and expected_msbuild_settings.
Note that for many settings, the VS2010 converter adds macros like
%(AdditionalIncludeDirectories) to make sure than inherited values are
included. Since the Gyp projects we generate do not use inheritance,
we removed these macros. They were:
ClCompile:
AdditionalIncludeDirectories: ';%(AdditionalIncludeDirectories)'
AdditionalOptions: ' %(AdditionalOptions)'
AdditionalUsingDirectories: ';%(AdditionalUsingDirectories)'
DisableSpecificWarnings: ';%(DisableSpecificWarnings)',
ForcedIncludeFiles: ';%(ForcedIncludeFiles)',
ForcedUsingFiles: ';%(ForcedUsingFiles)',
PreprocessorDefinitions: ';%(PreprocessorDefinitions)',
UndefinePreprocessorDefinitions:
';%(UndefinePreprocessorDefinitions)',
Link:
AdditionalDependencies: ';%(AdditionalDependencies)',
AdditionalLibraryDirectories: ';%(AdditionalLibraryDirectories)',
AdditionalManifestDependencies:
';%(AdditionalManifestDependencies)',
AdditionalOptions: ' %(AdditionalOptions)',
AddModuleNamesToAssembly: ';%(AddModuleNamesToAssembly)',
AssemblyLinkResource: ';%(AssemblyLinkResource)',
DelayLoadDLLs: ';%(DelayLoadDLLs)',
EmbedManagedResourceFile: ';%(EmbedManagedResourceFile)',
ForceSymbolReferences: ';%(ForceSymbolReferences)',
IgnoreSpecificDefaultLibraries:
';%(IgnoreSpecificDefaultLibraries)',
ResourceCompile:
AdditionalIncludeDirectories: ';%(AdditionalIncludeDirectories)',
AdditionalOptions: ' %(AdditionalOptions)',
PreprocessorDefinitions: ';%(PreprocessorDefinitions)',
Manifest:
AdditionalManifestFiles: ';%(AdditionalManifestFiles)',
AdditionalOptions: ' %(AdditionalOptions)',
InputResourceManifests: ';%(InputResourceManifests)',
"""
Note that for many settings, the VS2010 converter adds macros like
%(AdditionalIncludeDirectories) to make sure than inherited values are
included. Since the Gyp projects we generate do not use inheritance,
we removed these macros. They were:
ClCompile:
AdditionalIncludeDirectories: ';%(AdditionalIncludeDirectories)'
AdditionalOptions: ' %(AdditionalOptions)'
AdditionalUsingDirectories: ';%(AdditionalUsingDirectories)'
DisableSpecificWarnings: ';%(DisableSpecificWarnings)',
ForcedIncludeFiles: ';%(ForcedIncludeFiles)',
ForcedUsingFiles: ';%(ForcedUsingFiles)',
PreprocessorDefinitions: ';%(PreprocessorDefinitions)',
UndefinePreprocessorDefinitions:
';%(UndefinePreprocessorDefinitions)',
Link:
AdditionalDependencies: ';%(AdditionalDependencies)',
AdditionalLibraryDirectories: ';%(AdditionalLibraryDirectories)',
AdditionalManifestDependencies:
';%(AdditionalManifestDependencies)',
AdditionalOptions: ' %(AdditionalOptions)',
AddModuleNamesToAssembly: ';%(AddModuleNamesToAssembly)',
AssemblyLinkResource: ';%(AssemblyLinkResource)',
DelayLoadDLLs: ';%(DelayLoadDLLs)',
EmbedManagedResourceFile: ';%(EmbedManagedResourceFile)',
ForceSymbolReferences: ';%(ForceSymbolReferences)',
IgnoreSpecificDefaultLibraries:
';%(IgnoreSpecificDefaultLibraries)',
ResourceCompile:
AdditionalIncludeDirectories: ';%(AdditionalIncludeDirectories)',
AdditionalOptions: ' %(AdditionalOptions)',
PreprocessorDefinitions: ';%(PreprocessorDefinitions)',
Manifest:
AdditionalManifestFiles: ';%(AdditionalManifestFiles)',
AdditionalOptions: ' %(AdditionalOptions)',
InputResourceManifests: ';%(InputResourceManifests)',
"""
msvs_settings = {
"VCCLCompilerTool": {
"AdditionalIncludeDirectories": "dir1",
@ -1346,8 +1346,7 @@ class TestSequenceFunctions(unittest.TestCase):
"EmbedManifest": "false",
"GenerateCatalogFiles": "true",
"InputResourceManifests": "asfsfdafs",
"ManifestResourceFile":
"$(IntDir)\\$(TargetFileName).embed.manifest.resfdsf",
"ManifestResourceFile": "$(IntDir)\\$(TargetFileName).embed.manifest.resfdsf", # noqa: E501
"OutputManifestFile": "$(TargetPath).manifestdfs",
"RegistrarScriptFile": "sdfsfd",
"ReplacementsFile": "sdffsd",
@ -1531,8 +1530,7 @@ class TestSequenceFunctions(unittest.TestCase):
"LinkIncremental": "",
},
"ManifestResourceCompile": {
"ResourceOutputFileName":
"$(IntDir)$(TargetFileName).embed.manifest.resfdsf"
"ResourceOutputFileName": "$(IntDir)$(TargetFileName).embed.manifest.resfdsf" # noqa: E501
},
}
self.maxDiff = 9999 # on failure display a long diff

View file

@ -4,7 +4,7 @@
"""Visual Studio project reader/writer."""
import gyp.easy_xml as easy_xml
from gyp import easy_xml
class Writer:
@ -13,10 +13,10 @@ class Writer:
def __init__(self, tool_file_path, name):
"""Initializes the tool file.
Args:
tool_file_path: Path to the tool file.
name: Name of the tool file.
"""
Args:
tool_file_path: Path to the tool file.
name: Name of the tool file.
"""
self.tool_file_path = tool_file_path
self.name = name
self.rules_section = ["Rules"]
@ -26,14 +26,14 @@ class Writer:
):
"""Adds a rule to the tool file.
Args:
name: Name of the rule.
description: Description of the rule.
cmd: Command line of the rule.
additional_dependencies: other files which may trigger the rule.
outputs: outputs of the rule.
extensions: extensions handled by the rule.
"""
Args:
name: Name of the rule.
description: Description of the rule.
cmd: Command line of the rule.
additional_dependencies: other files which may trigger the rule.
outputs: outputs of the rule.
extensions: extensions handled by the rule.
"""
rule = [
"CustomBuildRule",
{

View file

@ -8,19 +8,18 @@ import os
import re
import socket # for gethostname
import gyp.easy_xml as easy_xml
from gyp import easy_xml
# ------------------------------------------------------------------------------
def _FindCommandInPath(command):
"""If there are no slashes in the command given, this function
searches the PATH env to find the given command, and converts it
to an absolute path. We have to do this because MSVS is looking
for an actual file to launch a debugger on, not just a command
line. Note that this happens at GYP time, so anything needing to
be built needs to have a full path."""
searches the PATH env to find the given command, and converts it
to an absolute path. We have to do this because MSVS is looking
for an actual file to launch a debugger on, not just a command
line. Note that this happens at GYP time, so anything needing to
be built needs to have a full path."""
if "/" in command or "\\" in command:
# If the command already has path elements (either relative or
# absolute), then assume it is constructed properly.
@ -59,11 +58,11 @@ class Writer:
def __init__(self, user_file_path, version, name):
"""Initializes the user file.
Args:
user_file_path: Path to the user file.
version: Version info.
name: Name of the user file.
"""
Args:
user_file_path: Path to the user file.
version: Version info.
name: Name of the user file.
"""
self.user_file_path = user_file_path
self.version = version
self.name = name
@ -72,9 +71,9 @@ class Writer:
def AddConfig(self, name):
"""Adds a configuration to the project.
Args:
name: Configuration name.
"""
Args:
name: Configuration name.
"""
self.configurations[name] = ["Configuration", {"Name": name}]
def AddDebugSettings(
@ -82,12 +81,12 @@ class Writer:
):
"""Adds a DebugSettings node to the user file for a particular config.
Args:
command: command line to run. First element in the list is the
executable. All elements of the command will be quoted if
necessary.
working_directory: other files which may trigger the rule. (optional)
"""
Args:
command: command line to run. First element in the list is the
executable. All elements of the command will be quoted if
necessary.
working_directory: other files which may trigger the rule. (optional)
"""
command = _QuoteWin32CommandLineArgs(command)
abs_command = _FindCommandInPath(command[0])

View file

@ -7,7 +7,6 @@
import copy
import os
# A dictionary mapping supported target types to extensions.
TARGET_TYPE_EXT = {
"executable": "exe",
@ -30,13 +29,13 @@ def _GetLargePdbShimCcPath():
def _DeepCopySomeKeys(in_dict, keys):
"""Performs a partial deep-copy on |in_dict|, only copying the keys in |keys|.
Arguments:
in_dict: The dictionary to copy.
keys: The keys to be copied. If a key is in this list and doesn't exist in
|in_dict| this is not an error.
Returns:
The partially deep-copied dictionary.
"""
Arguments:
in_dict: The dictionary to copy.
keys: The keys to be copied. If a key is in this list and doesn't exist in
|in_dict| this is not an error.
Returns:
The partially deep-copied dictionary.
"""
d = {}
for key in keys:
if key not in in_dict:
@ -48,12 +47,12 @@ def _DeepCopySomeKeys(in_dict, keys):
def _SuffixName(name, suffix):
"""Add a suffix to the end of a target.
Arguments:
name: name of the target (foo#target)
suffix: the suffix to be added
Returns:
Target name with suffix added (foo_suffix#target)
"""
Arguments:
name: name of the target (foo#target)
suffix: the suffix to be added
Returns:
Target name with suffix added (foo_suffix#target)
"""
parts = name.rsplit("#", 1)
parts[0] = f"{parts[0]}_{suffix}"
return "#".join(parts)
@ -62,24 +61,24 @@ def _SuffixName(name, suffix):
def _ShardName(name, number):
"""Add a shard number to the end of a target.
Arguments:
name: name of the target (foo#target)
number: shard number
Returns:
Target name with shard added (foo_1#target)
"""
Arguments:
name: name of the target (foo#target)
number: shard number
Returns:
Target name with shard added (foo_1#target)
"""
return _SuffixName(name, str(number))
def ShardTargets(target_list, target_dicts):
"""Shard some targets apart to work around the linkers limits.
Arguments:
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
Returns:
Tuple of the new sharded versions of the inputs.
"""
Arguments:
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
Returns:
Tuple of the new sharded versions of the inputs.
"""
# Gather the targets to shard, and how many pieces.
targets_to_shard = {}
for t in target_dicts:
@ -129,22 +128,22 @@ def ShardTargets(target_list, target_dicts):
def _GetPdbPath(target_dict, config_name, vars):
"""Returns the path to the PDB file that will be generated by a given
configuration.
configuration.
The lookup proceeds as follows:
- Look for an explicit path in the VCLinkerTool configuration block.
- Look for an 'msvs_large_pdb_path' variable.
- Use '<(PRODUCT_DIR)/<(product_name).(exe|dll).pdb' if 'product_name' is
specified.
- Use '<(PRODUCT_DIR)/<(target_name).(exe|dll).pdb'.
The lookup proceeds as follows:
- Look for an explicit path in the VCLinkerTool configuration block.
- Look for an 'msvs_large_pdb_path' variable.
- Use '<(PRODUCT_DIR)/<(product_name).(exe|dll).pdb' if 'product_name' is
specified.
- Use '<(PRODUCT_DIR)/<(target_name).(exe|dll).pdb'.
Arguments:
target_dict: The target dictionary to be searched.
config_name: The name of the configuration of interest.
vars: A dictionary of common GYP variables with generator-specific values.
Returns:
The path of the corresponding PDB file.
"""
Arguments:
target_dict: The target dictionary to be searched.
config_name: The name of the configuration of interest.
vars: A dictionary of common GYP variables with generator-specific values.
Returns:
The path of the corresponding PDB file.
"""
config = target_dict["configurations"][config_name]
msvs = config.setdefault("msvs_settings", {})
@ -169,16 +168,16 @@ def _GetPdbPath(target_dict, config_name, vars):
def InsertLargePdbShims(target_list, target_dicts, vars):
"""Insert a shim target that forces the linker to use 4KB pagesize PDBs.
This is a workaround for targets with PDBs greater than 1GB in size, the
limit for the 1KB pagesize PDBs created by the linker by default.
This is a workaround for targets with PDBs greater than 1GB in size, the
limit for the 1KB pagesize PDBs created by the linker by default.
Arguments:
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
vars: A dictionary of common GYP variables with generator-specific values.
Returns:
Tuple of the shimmed version of the inputs.
"""
Arguments:
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
vars: A dictionary of common GYP variables with generator-specific values.
Returns:
Tuple of the shimmed version of the inputs.
"""
# Determine which targets need shimming.
targets_to_shim = []
for t in target_dicts:

View file

@ -5,11 +5,11 @@
"""Handle version information related to Visual Stuio."""
import errno
import glob
import os
import re
import subprocess
import sys
import glob
def JoinPath(*args):
@ -69,25 +69,25 @@ class VisualStudioVersion:
def ProjectExtension(self):
"""Returns the file extension for the project."""
return self.uses_vcxproj and ".vcxproj" or ".vcproj"
return (self.uses_vcxproj and ".vcxproj") or ".vcproj"
def Path(self):
"""Returns the path to Visual Studio installation."""
return self.path
def ToolPath(self, tool):
"""Returns the path to a given compiler tool. """
"""Returns the path to a given compiler tool."""
return os.path.normpath(os.path.join(self.path, "VC/bin", tool))
def DefaultToolset(self):
"""Returns the msbuild toolset version that will be used in the absence
of a user override."""
of a user override."""
return self.default_toolset
def _SetupScriptInternal(self, target_arch):
"""Returns a command (with arguments) to be used to set up the
environment."""
assert target_arch in ("x86", "x64"), "target_arch not supported"
environment."""
assert target_arch in ("x86", "x64", "arm64"), "target_arch not supported"
# If WindowsSDKDir is set and SetEnv.Cmd exists then we are using the
# depot_tools build tools and should run SetEnv.Cmd to set up the
# environment. The check for WindowsSDKDir alone is not sufficient because
@ -109,8 +109,16 @@ class VisualStudioVersion:
)
# Always use a native executable, cross-compiling if necessary.
host_arch = "amd64" if is_host_arch_x64 else "x86"
msvc_target_arch = "amd64" if target_arch == "x64" else "x86"
host_arch = (
"amd64"
if is_host_arch_x64
else (
"arm64"
if os.environ.get("PROCESSOR_ARCHITECTURE") == "ARM64"
else "x86"
)
)
msvc_target_arch = {"x64": "amd64"}.get(target_arch, target_arch)
arg = host_arch
if host_arch != msvc_target_arch:
arg += "_" + msvc_target_arch
@ -154,16 +162,16 @@ class VisualStudioVersion:
def _RegistryQueryBase(sysdir, key, value):
"""Use reg.exe to read a particular key.
While ideally we might use the win32 module, we would like gyp to be
python neutral, so for instance cygwin python lacks this module.
While ideally we might use the win32 module, we would like gyp to be
python neutral, so for instance cygwin python lacks this module.
Arguments:
sysdir: The system subdirectory to attempt to launch reg.exe from.
key: The registry key to read from.
value: The particular value to read.
Return:
stdout from reg.exe, or None for failure.
"""
Arguments:
sysdir: The system subdirectory to attempt to launch reg.exe from.
key: The registry key to read from.
value: The particular value to read.
Return:
stdout from reg.exe, or None for failure.
"""
# Skip if not on Windows or Python Win32 setup issue
if sys.platform not in ("win32", "cygwin"):
return None
@ -184,20 +192,20 @@ def _RegistryQueryBase(sysdir, key, value):
def _RegistryQuery(key, value=None):
r"""Use reg.exe to read a particular key through _RegistryQueryBase.
First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If
that fails, it falls back to System32. Sysnative is available on Vista and
up and available on Windows Server 2003 and XP through KB patch 942589. Note
that Sysnative will always fail if using 64-bit python due to it being a
virtual directory and System32 will work correctly in the first place.
First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If
that fails, it falls back to System32. Sysnative is available on Vista and
up and available on Windows Server 2003 and XP through KB patch 942589. Note
that Sysnative will always fail if using 64-bit python due to it being a
virtual directory and System32 will work correctly in the first place.
KB 942589 - http://support.microsoft.com/kb/942589/en-us.
KB 942589 - http://support.microsoft.com/kb/942589/en-us.
Arguments:
key: The registry key.
value: The particular registry value to read (optional).
Return:
stdout from reg.exe, or None for failure.
"""
Arguments:
key: The registry key.
value: The particular registry value to read (optional).
Return:
stdout from reg.exe, or None for failure.
"""
text = None
try:
text = _RegistryQueryBase("Sysnative", key, value)
@ -212,14 +220,15 @@ def _RegistryQuery(key, value=None):
def _RegistryGetValueUsingWinReg(key, value):
"""Use the _winreg module to obtain the value of a registry key.
Args:
key: The registry key.
value: The particular registry value to read.
Return:
contents of the registry key's value, or None on failure. Throws
ImportError if winreg is unavailable.
"""
from winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx
Args:
key: The registry key.
value: The particular registry value to read.
Return:
contents of the registry key's value, or None on failure. Throws
ImportError if winreg is unavailable.
"""
from winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx # noqa: PLC0415
try:
root, subkey = key.split("\\", 1)
assert root == "HKLM" # Only need HKLM for now.
@ -232,17 +241,17 @@ def _RegistryGetValueUsingWinReg(key, value):
def _RegistryGetValue(key, value):
"""Use _winreg or reg.exe to obtain the value of a registry key.
Using _winreg is preferable because it solves an issue on some corporate
environments where access to reg.exe is locked down. However, we still need
to fallback to reg.exe for the case where the _winreg module is not available
(for example in cygwin python).
Using _winreg is preferable because it solves an issue on some corporate
environments where access to reg.exe is locked down. However, we still need
to fallback to reg.exe for the case where the _winreg module is not available
(for example in cygwin python).
Args:
key: The registry key.
value: The particular registry value to read.
Return:
contents of the registry key's value, or None on failure.
"""
Args:
key: The registry key.
value: The particular registry value to read.
Return:
contents of the registry key's value, or None on failure.
"""
try:
return _RegistryGetValueUsingWinReg(key, value)
except ImportError:
@ -262,13 +271,25 @@ def _RegistryGetValue(key, value):
def _CreateVersion(name, path, sdk_based=False):
"""Sets up MSVS project generation.
Setup is based off the GYP_MSVS_VERSION environment variable or whatever is
autodetected if GYP_MSVS_VERSION is not explicitly specified. If a version is
passed in that doesn't match a value in versions python will throw a error.
"""
Setup is based off the GYP_MSVS_VERSION environment variable or whatever is
autodetected if GYP_MSVS_VERSION is not explicitly specified. If a version is
passed in that doesn't match a value in versions python will throw a error.
"""
if path:
path = os.path.normpath(path)
versions = {
"2026": VisualStudioVersion(
"2026",
"Visual Studio 2026",
solution_version="12.00",
project_version="18.0",
flat_sln=False,
uses_vcxproj=True,
path=path,
sdk_based=sdk_based,
default_toolset="v145",
compatible_sdks=["v8.1", "v10.0"],
),
"2022": VisualStudioVersion(
"2022",
"Visual Studio 2022",
@ -435,22 +456,22 @@ def _ConvertToCygpath(path):
def _DetectVisualStudioVersions(versions_to_check, force_express):
"""Collect the list of installed visual studio versions.
Returns:
A list of visual studio versions installed in descending order of
usage preference.
Base this on the registry and a quick check if devenv.exe exists.
Possibilities are:
2005(e) - Visual Studio 2005 (8)
2008(e) - Visual Studio 2008 (9)
2010(e) - Visual Studio 2010 (10)
2012(e) - Visual Studio 2012 (11)
2013(e) - Visual Studio 2013 (12)
2015 - Visual Studio 2015 (14)
2017 - Visual Studio 2017 (15)
2019 - Visual Studio 2019 (16)
2022 - Visual Studio 2022 (17)
Where (e) is e for express editions of MSVS and blank otherwise.
"""
Returns:
A list of visual studio versions installed in descending order of
usage preference.
Base this on the registry and a quick check if devenv.exe exists.
Possibilities are:
2005(e) - Visual Studio 2005 (8)
2008(e) - Visual Studio 2008 (9)
2010(e) - Visual Studio 2010 (10)
2012(e) - Visual Studio 2012 (11)
2013(e) - Visual Studio 2013 (12)
2015 - Visual Studio 2015 (14)
2017 - Visual Studio 2017 (15)
2019 - Visual Studio 2019 (16)
2022 - Visual Studio 2022 (17)
Where (e) is e for express editions of MSVS and blank otherwise.
"""
version_to_year = {
"8.0": "2005",
"9.0": "2008",
@ -461,6 +482,7 @@ def _DetectVisualStudioVersions(versions_to_check, force_express):
"15.0": "2017",
"16.0": "2019",
"17.0": "2022",
"18.0": "2026",
}
versions = []
for version in versions_to_check:
@ -527,16 +549,27 @@ def _DetectVisualStudioVersions(versions_to_check, force_express):
def SelectVisualStudioVersion(version="auto", allow_fallback=True):
"""Select which version of Visual Studio projects to generate.
Arguments:
version: Hook to allow caller to force a particular version (vs auto).
Returns:
An object representing a visual studio project format version.
"""
Arguments:
version: Hook to allow caller to force a particular version (vs auto).
Returns:
An object representing a visual studio project format version.
"""
# In auto mode, check environment variable for override.
if version == "auto":
version = os.environ.get("GYP_MSVS_VERSION", "auto")
version_map = {
"auto": ("17.0", "16.0", "15.0", "14.0", "12.0", "10.0", "9.0", "8.0", "11.0"),
"auto": (
"18.0",
"17.0",
"16.0",
"15.0",
"14.0",
"12.0",
"10.0",
"9.0",
"8.0",
"11.0",
),
"2005": ("8.0",),
"2005e": ("8.0",),
"2008": ("9.0",),
@ -551,9 +584,9 @@ def SelectVisualStudioVersion(version="auto", allow_fallback=True):
"2017": ("15.0",),
"2019": ("16.0",),
"2022": ("17.0",),
"2026": ("18.0",),
}
override_path = os.environ.get("GYP_MSVS_OVERRIDE_PATH")
if override_path:
if override_path := os.environ.get("GYP_MSVS_OVERRIDE_PATH"):
msvs_version = os.environ.get("GYP_MSVS_VERSION")
if not msvs_version:
raise ValueError(

View file

@ -4,17 +4,19 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import annotations
import copy
import gyp.input
import argparse
import copy
import os.path
import re
import shlex
import sys
import traceback
from gyp.common import GypError
from importlib.metadata import version
import gyp.input
from gyp.common import GypError
# Default debug modes for GYP
debug = {}
@ -25,6 +27,20 @@ DEBUG_VARIABLES = "variables"
DEBUG_INCLUDES = "includes"
def EscapeForCString(string: bytes | str) -> str:
if isinstance(string, str):
string = string.encode(encoding="utf8")
backslash_or_double_quote = {ord("\\"), ord('"')}
result = ""
for char in string:
if char in backslash_or_double_quote or not 32 <= char < 127:
result += "\\%03o" % char
else:
result += chr(char)
return result
def DebugOutput(mode, message, *args):
if "all" in gyp.debug or mode in gyp.debug:
ctx = ("unknown", 0, "unknown")
@ -63,11 +79,11 @@ def Load(
circular_check=True,
):
"""
Loads one or more specified build files.
default_variables and includes will be copied before use.
Returns the generator for the specified format and the
data returned by loading the specified build files.
"""
Loads one or more specified build files.
default_variables and includes will be copied before use.
Returns the generator for the specified format and the
data returned by loading the specified build files.
"""
if params is None:
params = {}
@ -101,21 +117,24 @@ def Load(
# These parameters are passed in order (as opposed to by key)
# because ActivePython cannot handle key parameters to __import__.
generator = __import__(generator_name, globals(), locals(), generator_name)
for (key, val) in generator.generator_default_variables.items():
for key, val in generator.generator_default_variables.items():
default_variables.setdefault(key, val)
output_dir = params["options"].generator_output or params["options"].toplevel_dir
if default_variables["GENERATOR"] == "ninja":
default_variables.setdefault(
"PRODUCT_DIR_ABS",
os.path.join(output_dir, "out", default_variables["build_type"]),
product_dir_abs = os.path.join(
output_dir, "out", default_variables.get("build_type", "default")
)
else:
default_variables.setdefault(
"PRODUCT_DIR_ABS",
os.path.join(output_dir, default_variables["CONFIGURATION_NAME"]),
product_dir_abs = os.path.join(
output_dir, default_variables["CONFIGURATION_NAME"]
)
default_variables.setdefault("PRODUCT_DIR_ABS", product_dir_abs)
default_variables.setdefault(
"PRODUCT_DIR_ABS_CSTR", EscapeForCString(product_dir_abs)
)
# Give the generator the opportunity to set additional variables based on
# the params it will receive in the output phase.
if getattr(generator, "CalculateVariables", None):
@ -168,10 +187,10 @@ def Load(
def NameValueListToDict(name_value_list):
"""
Takes an array of strings of the form 'NAME=VALUE' and creates a dictionary
of the pairs. If a string is simply NAME, then the value in the dictionary
is set to True. If VALUE can be converted to an integer, it is.
"""
Takes an array of strings of the form 'NAME=VALUE' and creates a dictionary
of the pairs. If a string is simply NAME, then the value in the dictionary
is set to True. If VALUE can be converted to an integer, it is.
"""
result = {}
for item in name_value_list:
tokens = item.split("=", 1)
@ -190,8 +209,7 @@ def NameValueListToDict(name_value_list):
def ShlexEnv(env_name):
flags = os.environ.get(env_name, [])
if flags:
if flags := os.environ.get(env_name) or []:
flags = shlex.split(flags)
return flags
@ -205,13 +223,13 @@ def FormatOpt(opt, value):
def RegenerateAppendFlag(flag, values, predicate, env_name, options):
"""Regenerate a list of command line flags, for an option of action='append'.
The |env_name|, if given, is checked in the environment and used to generate
an initial list of options, then the options that were specified on the
command line (given in |values|) are appended. This matches the handling of
environment variables and command line flags where command line flags override
the environment, while not requiring the environment to be set when the flags
are used again.
"""
The |env_name|, if given, is checked in the environment and used to generate
an initial list of options, then the options that were specified on the
command line (given in |values|) are appended. This matches the handling of
environment variables and command line flags where command line flags override
the environment, while not requiring the environment to be set when the flags
are used again.
"""
flags = []
if options.use_environment and env_name:
for flag_value in ShlexEnv(env_name):
@ -227,14 +245,14 @@ def RegenerateAppendFlag(flag, values, predicate, env_name, options):
def RegenerateFlags(options):
"""Given a parsed options object, and taking the environment variables into
account, returns a list of flags that should regenerate an equivalent options
object (even in the absence of the environment variables.)
account, returns a list of flags that should regenerate an equivalent options
object (even in the absence of the environment variables.)
Any path options will be normalized relative to depth.
Any path options will be normalized relative to depth.
The format flag is not included, as it is assumed the calling generator will
set that as appropriate.
"""
The format flag is not included, as it is assumed the calling generator will
set that as appropriate.
"""
def FixPath(path):
path = gyp.common.FixIfRelativePath(path, options.depth)
@ -251,7 +269,7 @@ def RegenerateFlags(options):
for name, metadata in options._regeneration_metadata.items():
opt = metadata["opt"]
value = getattr(options, name)
value_predicate = metadata["type"] == "path" and FixPath or Noop
value_predicate = (metadata["type"] == "path" and FixPath) or Noop
action = metadata["action"]
env_name = metadata["env_name"]
if action == "append":
@ -292,15 +310,15 @@ class RegeneratableOptionParser(argparse.ArgumentParser):
def add_argument(self, *args, **kw):
"""Add an option to the parser.
This accepts the same arguments as ArgumentParser.add_argument, plus the
following:
regenerate: can be set to False to prevent this option from being included
in regeneration.
env_name: name of environment variable that additional values for this
option come from.
type: adds type='path', to tell the regenerator that the values of
this option need to be made relative to options.depth
"""
This accepts the same arguments as ArgumentParser.add_argument, plus the
following:
regenerate: can be set to False to prevent this option from being included
in regeneration.
env_name: name of environment variable that additional values for this
option come from.
type: adds type='path', to tell the regenerator that the values of
this option need to be made relative to options.depth
"""
env_name = kw.pop("env_name", None)
if "dest" in kw and kw.pop("regenerate", True):
dest = kw["dest"]
@ -328,7 +346,7 @@ class RegeneratableOptionParser(argparse.ArgumentParser):
def gyp_main(args):
my_name = os.path.basename(sys.argv[0])
usage = "usage: %(prog)s [options ...] [build_file ...]"
usage = "%(prog)s [options ...] [build_file ...]"
parser = RegeneratableOptionParser(usage=usage.replace("%s", "%(prog)s"))
parser.add_argument(
@ -346,7 +364,7 @@ def gyp_main(args):
action="store",
env_name="GYP_CONFIG_DIR",
default=None,
help="The location for configuration files like " "include.gypi.",
help="The location for configuration files like include.gypi.",
)
parser.add_argument(
"-d",
@ -474,8 +492,7 @@ def gyp_main(args):
options, build_files_arg = parser.parse_args(args)
if options.version:
import pkg_resources
print(f"v{pkg_resources.get_distribution('gyp-next').version}")
print(f"v{version('gyp-next')}")
return 0
build_files = build_files_arg
@ -510,19 +527,18 @@ def gyp_main(args):
# If no format was given on the command line, then check the env variable.
generate_formats = []
if options.use_environment:
generate_formats = os.environ.get("GYP_GENERATORS", [])
generate_formats = os.environ.get("GYP_GENERATORS") or []
if generate_formats:
generate_formats = re.split(r"[\s,]", generate_formats)
if generate_formats:
options.formats = generate_formats
# Nothing in the variable, default based on platform.
elif sys.platform == "darwin":
options.formats = ["xcode"]
elif sys.platform in ("win32", "cygwin"):
options.formats = ["msvs"]
else:
# Nothing in the variable, default based on platform.
if sys.platform == "darwin":
options.formats = ["xcode"]
elif sys.platform in ("win32", "cygwin"):
options.formats = ["msvs"]
else:
options.formats = ["make"]
options.formats = ["make"]
if not options.generator_output and options.use_environment:
g_o = os.environ.get("GYP_GENERATOR_OUTPUT")
@ -622,7 +638,7 @@ def gyp_main(args):
if options.generator_flags:
gen_flags += options.generator_flags
generator_flags = NameValueListToDict(gen_flags)
if DEBUG_GENERAL in gyp.debug.keys():
if DEBUG_GENERAL in gyp.debug:
DebugOutput(DEBUG_GENERAL, "generator_flags: %s", generator_flags)
# Generate all requested formats (use a set in case we got one format request
@ -681,7 +697,7 @@ def main(args):
return 1
# NOTE: setuptools generated console_scripts calls function with no arguments
# NOTE: console_scripts calls this function with no arguments
def script_main():
return main(sys.argv[1:])

View file

@ -6,10 +6,10 @@ import errno
import filecmp
import os.path
import re
import tempfile
import sys
import shlex
import subprocess
import sys
import tempfile
from collections.abc import MutableSet
@ -31,10 +31,8 @@ class memoize:
class GypError(Exception):
"""Error class representing an error, which is to be presented
to the user. The main entry point will catch and display this.
"""
pass
to the user. The main entry point will catch and display this.
"""
def ExceptionAppend(e, msg):
@ -49,9 +47,9 @@ def ExceptionAppend(e, msg):
def FindQualifiedTargets(target, qualified_list):
"""
Given a list of qualified targets, return the qualified targets for the
specified |target|.
"""
Given a list of qualified targets, return the qualified targets for the
specified |target|.
"""
return [t for t in qualified_list if ParseQualifiedTarget(t)[1] == target]
@ -116,7 +114,7 @@ def BuildFile(fully_qualified_target):
def GetEnvironFallback(var_list, default):
"""Look up a key in the environment, with fallback to secondary keys
and finally falling back to a default value."""
and finally falling back to a default value."""
for var in var_list:
if var in os.environ:
return os.environ[var]
@ -144,20 +142,16 @@ def RelativePath(path, relative_to, follow_path_symlink=True):
# symlink, this option has no effect.
# Convert to normalized (and therefore absolute paths).
if follow_path_symlink:
path = os.path.realpath(path)
else:
path = os.path.abspath(path)
path = os.path.realpath(path) if follow_path_symlink else os.path.abspath(path)
relative_to = os.path.realpath(relative_to)
# On Windows, we can't create a relative path to a different drive, so just
# use the absolute path.
if sys.platform == "win32":
if (
os.path.splitdrive(path)[0].lower()
!= os.path.splitdrive(relative_to)[0].lower()
):
return path
if sys.platform == "win32" and (
os.path.splitdrive(path)[0].lower()
!= os.path.splitdrive(relative_to)[0].lower()
):
return path
# Split the paths into components.
path_split = path.split(os.path.sep)
@ -183,11 +177,11 @@ def RelativePath(path, relative_to, follow_path_symlink=True):
@memoize
def InvertRelativePath(path, toplevel_dir=None):
"""Given a path like foo/bar that is relative to toplevel_dir, return
the inverse relative path back to the toplevel_dir.
the inverse relative path back to the toplevel_dir.
E.g. os.path.normpath(os.path.join(path, InvertRelativePath(path)))
should always produce the empty string, unless the path contains symlinks.
"""
E.g. os.path.normpath(os.path.join(path, InvertRelativePath(path)))
should always produce the empty string, unless the path contains symlinks.
"""
if not path:
return path
toplevel_dir = "." if toplevel_dir is None else toplevel_dir
@ -267,20 +261,17 @@ _escape = re.compile(r'(["\\`])')
def EncodePOSIXShellArgument(argument):
"""Encodes |argument| suitably for consumption by POSIX shells.
argument may be quoted and escaped as necessary to ensure that POSIX shells
treat the returned value as a literal representing the argument passed to
this function. Parameter (variable) expansions beginning with $ are allowed
to remain intact without escaping the $, to allow the argument to contain
references to variables to be expanded by the shell.
"""
argument may be quoted and escaped as necessary to ensure that POSIX shells
treat the returned value as a literal representing the argument passed to
this function. Parameter (variable) expansions beginning with $ are allowed
to remain intact without escaping the $, to allow the argument to contain
references to variables to be expanded by the shell.
"""
if not isinstance(argument, str):
argument = str(argument)
if _quote.search(argument):
quote = '"'
else:
quote = ""
quote = '"' if _quote.search(argument) else ""
encoded = quote + re.sub(_escape, r"\\\1", argument) + quote
@ -290,9 +281,9 @@ def EncodePOSIXShellArgument(argument):
def EncodePOSIXShellList(list):
"""Encodes |list| suitably for consumption by POSIX shells.
Returns EncodePOSIXShellArgument for each item in list, and joins them
together using the space character as an argument separator.
"""
Returns EncodePOSIXShellArgument for each item in list, and joins them
together using the space character as an argument separator.
"""
encoded_arguments = []
for argument in list:
@ -320,14 +311,12 @@ def DeepDependencyTargets(target_dicts, roots):
def BuildFileTargets(target_list, build_file):
"""From a target_list, returns the subset from the specified build_file.
"""
"""From a target_list, returns the subset from the specified build_file."""
return [p for p in target_list if BuildFile(p) == build_file]
def AllTargets(target_list, target_dicts, build_file):
"""Returns all targets (direct and dependencies) for the specified build_file.
"""
"""Returns all targets (direct and dependencies) for the specified build_file."""
bftargets = BuildFileTargets(target_list, build_file)
deptargets = DeepDependencyTargets(target_dicts, bftargets)
return bftargets + deptargets
@ -336,12 +325,12 @@ def AllTargets(target_list, target_dicts, build_file):
def WriteOnDiff(filename):
"""Write to a file only if the new contents differ.
Arguments:
filename: name of the file to potentially write to.
Returns:
A file like object which will write to temporary file and only overwrite
the target if it differs (on close).
"""
Arguments:
filename: name of the file to potentially write to.
Returns:
A file like object which will write to temporary file and only overwrite
the target if it differs (on close).
"""
class Writer:
"""Wrapper around file which only covers the target if it differs."""
@ -430,7 +419,70 @@ def EnsureDirExists(path):
pass
def GetFlavor(params):
def GetCompilerPredefines(): # -> dict
cmd = []
defines = {}
# shlex.split() will eat '\' in posix mode, but
# setting posix=False will preserve extra '"' cause CreateProcess fail on Windows
# this makes '\' in %CC_target% and %CFLAGS% work
def replace_sep(s):
return s.replace(os.sep, "/") if os.sep != "/" else s
if CC := os.environ.get("CC_target") or os.environ.get("CC"):
cmd += shlex.split(replace_sep(CC))
if CFLAGS := os.environ.get("CFLAGS"):
cmd += shlex.split(replace_sep(CFLAGS))
elif CXX := os.environ.get("CXX_target") or os.environ.get("CXX"):
cmd += shlex.split(replace_sep(CXX))
if CXXFLAGS := os.environ.get("CXXFLAGS"):
cmd += shlex.split(replace_sep(CXXFLAGS))
else:
return defines
if sys.platform == "win32":
fd, input = tempfile.mkstemp(suffix=".c")
real_cmd = [*cmd, "-dM", "-E", "-x", "c", input]
try:
os.close(fd)
stdout = subprocess.run(
real_cmd, shell=True, capture_output=True, check=True
).stdout
except subprocess.CalledProcessError as e:
print(
"Warning: failed to get compiler predefines\n"
"cmd: %s\n"
"status: %d" % (e.cmd, e.returncode),
file=sys.stderr,
)
return defines
finally:
os.unlink(input)
else:
input = "/dev/null"
real_cmd = [*cmd, "-dM", "-E", "-x", "c", input]
try:
stdout = subprocess.run(
real_cmd, shell=False, capture_output=True, check=True
).stdout
except subprocess.CalledProcessError as e:
print(
"Warning: failed to get compiler predefines\n"
"cmd: %s\n"
"status: %d" % (e.cmd, e.returncode),
file=sys.stderr,
)
return defines
lines = stdout.decode("utf-8").replace("\r\n", "\n").split("\n")
for line in lines:
if (line or "").startswith("#define "):
_, key, *value = line.split(" ")
defines[key] = " ".join(value)
return defines
def GetFlavorByPlatform():
"""Returns |params.flavor| if it's set, the system's default flavor else."""
flavors = {
"cygwin": "win",
@ -438,8 +490,6 @@ def GetFlavor(params):
"darwin": "mac",
}
if "flavor" in params:
return params["flavor"]
if sys.platform in flavors:
return flavors[sys.platform]
if sys.platform.startswith("sunos"):
@ -460,9 +510,22 @@ def GetFlavor(params):
return "linux"
def GetFlavor(params):
if "flavor" in params:
return params["flavor"]
defines = GetCompilerPredefines()
if "__EMSCRIPTEN__" in defines:
return "emscripten"
if "__wasm__" in defines:
return "wasi" if "__wasi__" in defines else "wasm"
return GetFlavorByPlatform()
def CopyTool(flavor, out_path, generator_flags={}):
"""Finds (flock|mac|win)_tool.gyp in the gyp directory and copies it
to |out_path|."""
to |out_path|."""
# aix and solaris just need flock emulation. mac and win use more complicated
# support scripts.
prefix = {
@ -518,7 +581,8 @@ def uniquer(seq, idfun=lambda x: x):
# Based on http://code.activestate.com/recipes/576694/.
class OrderedSet(MutableSet):
class OrderedSet(MutableSet): # noqa: PLW1641
# TODO (cclauss): Fix eq-without-hash ruff rule PLW1641
def __init__(self, iterable=None):
self.end = end = []
end += [None, end, end] # sentinel node for doubly linked list
@ -596,24 +660,24 @@ class CycleError(Exception):
def TopologicallySorted(graph, get_edges):
r"""Topologically sort based on a user provided edge definition.
Args:
graph: A list of node names.
get_edges: A function mapping from node name to a hashable collection
of node names which this node has outgoing edges to.
Returns:
A list containing all of the node in graph in topological order.
It is assumed that calling get_edges once for each node and caching is
cheaper than repeatedly calling get_edges.
Raises:
CycleError in the event of a cycle.
Example:
graph = {'a': '$(b) $(c)', 'b': 'hi', 'c': '$(b)'}
def GetEdges(node):
return re.findall(r'\$\(([^))]\)', graph[node])
print TopologicallySorted(graph.keys(), GetEdges)
==>
['a', 'c', b']
"""
Args:
graph: A list of node names.
get_edges: A function mapping from node name to a hashable collection
of node names which this node has outgoing edges to.
Returns:
A list containing all of the node in graph in topological order.
It is assumed that calling get_edges once for each node and caching is
cheaper than repeatedly calling get_edges.
Raises:
CycleError in the event of a cycle.
Example:
graph = {'a': '$(b) $(c)', 'b': 'hi', 'c': '$(b)'}
def GetEdges(node):
return re.findall(r'\$\(([^))]\)', graph[node])
print TopologicallySorted(graph.keys(), GetEdges)
==>
['a', 'c', b']
"""
get_edges = memoize(get_edges)
visited = set()
visiting = set()

View file

@ -6,9 +6,13 @@
"""Unit tests for the common.py file."""
import gyp.common
import unittest
import os
import subprocess
import sys
import unittest
from unittest.mock import MagicMock, patch
import gyp.common
class TestTopologicallySorted(unittest.TestCase):
@ -24,9 +28,12 @@ class TestTopologicallySorted(unittest.TestCase):
def GetEdge(node):
return tuple(graph[node])
self.assertEqual(
gyp.common.TopologicallySorted(graph.keys(), GetEdge), ["a", "c", "d", "b"]
)
assert gyp.common.TopologicallySorted(graph.keys(), GetEdge) == [
"a",
"c",
"d",
"b",
]
def test_Cycle(self):
"""Test that an exception is thrown on a cyclic graph."""
@ -58,7 +65,7 @@ class TestGetFlavor(unittest.TestCase):
def assertFlavor(self, expected, argument, param):
sys.platform = argument
self.assertEqual(expected, gyp.common.GetFlavor(param))
assert expected == gyp.common.GetFlavor(param)
def test_platform_default(self):
self.assertFlavor("freebsd", "freebsd9", {})
@ -73,6 +80,107 @@ class TestGetFlavor(unittest.TestCase):
def test_param(self):
self.assertFlavor("foobar", "linux2", {"flavor": "foobar"})
class MockCommunicate:
def __init__(self, stdout):
self.stdout = stdout
def decode(self, encoding):
return self.stdout
@patch("os.close")
@patch("os.unlink")
@patch("tempfile.mkstemp")
def test_GetCompilerPredefines(self, mock_mkstemp, mock_unlink, mock_close):
mock_close.return_value = None
mock_unlink.return_value = None
mock_mkstemp.return_value = (0, "temp.c")
def mock_run(env, defines_stdout, expected_cmd, throws=False):
with patch("subprocess.run") as mock_run:
expected_input = "temp.c" if sys.platform == "win32" else "/dev/null"
if throws:
mock_run.side_effect = subprocess.CalledProcessError(
returncode=1,
cmd=[*expected_cmd, "-dM", "-E", "-x", "c", expected_input],
)
else:
mock_process = MagicMock()
mock_process.returncode = 0
mock_process.stdout = TestGetFlavor.MockCommunicate(defines_stdout)
mock_run.return_value = mock_process
with patch.dict(os.environ, env):
try:
defines = gyp.common.GetCompilerPredefines()
except Exception as e:
self.fail(f"GetCompilerPredefines raised an exception: {e}")
flavor = gyp.common.GetFlavor({})
if env.get("CC_target") or env.get("CC"):
mock_run.assert_called_with(
[*expected_cmd, "-dM", "-E", "-x", "c", expected_input],
shell=sys.platform == "win32",
capture_output=True,
check=True,
)
return [defines, flavor]
[defines0, _] = mock_run({"CC": "cl.exe"}, "", ["cl.exe"], True)
assert defines0 == {}
[defines1, _] = mock_run({}, "", [])
assert defines1 == {}
[defines2, flavor2] = mock_run(
{"CC_target": "/opt/wasi-sdk/bin/clang"},
"#define __wasm__ 1\n#define __wasi__ 1\n",
["/opt/wasi-sdk/bin/clang"],
)
assert defines2 == {"__wasm__": "1", "__wasi__": "1"}
assert flavor2 == "wasi"
[defines3, flavor3] = mock_run(
{"CC_target": "/opt/wasi-sdk/bin/clang --target=wasm32"},
"#define __wasm__ 1\n",
["/opt/wasi-sdk/bin/clang", "--target=wasm32"],
)
assert defines3 == {"__wasm__": "1"}
assert flavor3 == "wasm"
[defines4, flavor4] = mock_run(
{"CC_target": "/emsdk/upstream/emscripten/emcc"},
"#define __EMSCRIPTEN__ 1\n",
["/emsdk/upstream/emscripten/emcc"],
)
assert defines4 == {"__EMSCRIPTEN__": "1"}
assert flavor4 == "emscripten"
# Test path which include white space
[defines5, flavor5] = mock_run(
{
"CC_target": '"/Users/Toyo Li/wasi-sdk/bin/clang" -O3',
"CFLAGS": "--target=wasm32-wasi-threads -pthread",
},
"#define __wasm__ 1\n#define __wasi__ 1\n#define _REENTRANT 1\n",
[
"/Users/Toyo Li/wasi-sdk/bin/clang",
"-O3",
"--target=wasm32-wasi-threads",
"-pthread",
],
)
assert defines5 == {"__wasm__": "1", "__wasi__": "1", "_REENTRANT": "1"}
assert flavor5 == "wasi"
original_sep = os.sep
os.sep = "\\"
[defines6, flavor6] = mock_run(
{"CC_target": '"C:\\Program Files\\wasi-sdk\\clang.exe"'},
"#define __wasm__ 1\n#define __wasi__ 1\n",
["C:/Program Files/wasi-sdk/clang.exe"],
)
os.sep = original_sep
assert defines6 == {"__wasm__": "1", "__wasi__": "1"}
assert flavor6 == "wasi"
if __name__ == "__main__":
unittest.main()

View file

@ -2,51 +2,51 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import sys
import re
import os
import locale
import os
import re
import sys
from functools import reduce
def XmlToString(content, encoding="utf-8", pretty=False):
""" Writes the XML content to disk, touching the file only if it has changed.
"""Writes the XML content to disk, touching the file only if it has changed.
Visual Studio files have a lot of pre-defined structures. This function makes
it easy to represent these structures as Python data structures, instead of
having to create a lot of function calls.
Visual Studio files have a lot of pre-defined structures. This function makes
it easy to represent these structures as Python data structures, instead of
having to create a lot of function calls.
Each XML element of the content is represented as a list composed of:
1. The name of the element, a string,
2. The attributes of the element, a dictionary (optional), and
3+. The content of the element, if any. Strings are simple text nodes and
lists are child elements.
Each XML element of the content is represented as a list composed of:
1. The name of the element, a string,
2. The attributes of the element, a dictionary (optional), and
3+. The content of the element, if any. Strings are simple text nodes and
lists are child elements.
Example 1:
<test/>
becomes
['test']
Example 1:
<test/>
becomes
['test']
Example 2:
<myelement a='value1' b='value2'>
<childtype>This is</childtype>
<childtype>it!</childtype>
</myelement>
Example 2:
<myelement a='value1' b='value2'>
<childtype>This is</childtype>
<childtype>it!</childtype>
</myelement>
becomes
['myelement', {'a':'value1', 'b':'value2'},
['childtype', 'This is'],
['childtype', 'it!'],
]
becomes
['myelement', {'a':'value1', 'b':'value2'},
['childtype', 'This is'],
['childtype', 'it!'],
]
Args:
content: The structured content to be converted.
encoding: The encoding to report on the first XML line.
pretty: True if we want pretty printing with indents and new lines.
Args:
content: The structured content to be converted.
encoding: The encoding to report on the first XML line.
pretty: True if we want pretty printing with indents and new lines.
Returns:
The XML content as a string.
"""
Returns:
The XML content as a string.
"""
# We create a huge list of all the elements of the file.
xml_parts = ['<?xml version="1.0" encoding="%s"?>' % encoding]
if pretty:
@ -58,14 +58,14 @@ def XmlToString(content, encoding="utf-8", pretty=False):
def _ConstructContentList(xml_parts, specification, pretty, level=0):
""" Appends the XML parts corresponding to the specification.
"""Appends the XML parts corresponding to the specification.
Args:
xml_parts: A list of XML parts to be appended to.
specification: The specification of the element. See EasyXml docs.
pretty: True if we want pretty printing with indents and new lines.
level: Indentation level.
"""
Args:
xml_parts: A list of XML parts to be appended to.
specification: The specification of the element. See EasyXml docs.
pretty: True if we want pretty printing with indents and new lines.
level: Indentation level.
"""
# The first item in a specification is the name of the element.
if pretty:
indentation = " " * level
@ -107,21 +107,26 @@ def _ConstructContentList(xml_parts, specification, pretty, level=0):
xml_parts.append("/>%s" % new_line)
def WriteXmlIfChanged(content, path, encoding="utf-8", pretty=False,
win32=(sys.platform == "win32")):
""" Writes the XML content to disk, touching the file only if it has changed.
def WriteXmlIfChanged(
content, path, encoding="utf-8", pretty=False, win32=(sys.platform == "win32")
):
"""Writes the XML content to disk, touching the file only if it has changed.
Args:
content: The structured content to be written.
path: Location of the file.
encoding: The encoding to report on the first line of the XML file.
pretty: True if we want pretty printing with indents and new lines.
"""
Args:
content: The structured content to be written.
path: Location of the file.
encoding: The encoding to report on the first line of the XML file.
pretty: True if we want pretty printing with indents and new lines.
"""
xml_string = XmlToString(content, encoding, pretty)
if win32 and os.linesep != "\r\n":
xml_string = xml_string.replace("\n", "\r\n")
default_encoding = locale.getdefaultlocale()[1]
try: # getdefaultlocale() was removed in Python 3.11
default_encoding = locale.getdefaultlocale()[1]
except AttributeError:
default_encoding = locale.getencoding()
if default_encoding and default_encoding.upper() != encoding.upper():
xml_string = xml_string.encode(encoding)
@ -153,7 +158,7 @@ _xml_escape_re = re.compile("(%s)" % "|".join(map(re.escape, _xml_escape_map.key
def _XmlEscape(value, attr=False):
""" Escape a string for inclusion in XML."""
"""Escape a string for inclusion in XML."""
def replace(match):
m = match.string[match.start() : match.end()]

View file

@ -4,13 +4,13 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Unit tests for the easy_xml.py file. """
"""Unit tests for the easy_xml.py file."""
import gyp.easy_xml as easy_xml
import unittest
from io import StringIO
from gyp import easy_xml
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
@ -76,6 +76,8 @@ class TestSequenceFunctions(unittest.TestCase):
'\'Debug|Win32\'" Label="Configuration">'
"<ConfigurationType>Application</ConfigurationType>"
"<CharacterSet>Unicode</CharacterSet>"
"<SpectreMitigation>SpectreLoadCF</SpectreMitigation>"
"<VCToolsVersion>14.36.32532</VCToolsVersion>"
"</PropertyGroup>"
"</Project>"
)
@ -99,6 +101,8 @@ class TestSequenceFunctions(unittest.TestCase):
},
["ConfigurationType", "Application"],
["CharacterSet", "Unicode"],
["SpectreMitigation", "SpectreLoadCF"],
["VCToolsVersion", "14.36.32532"],
],
]
)

View file

@ -62,12 +62,12 @@ directly supplied to gyp. OTOH if both "a.gyp" and "b.gyp" are supplied to gyp
then the "all" target includes "b1" and "b2".
"""
import gyp.common
import json
import os
import posixpath
import gyp.common
debug = False
found_dependency_string = "Found dependency"
@ -129,8 +129,8 @@ def _ToGypPath(path):
def _ResolveParent(path, base_path_components):
"""Resolves |path|, which starts with at least one '../'. Returns an empty
string if the path shouldn't be considered. See _AddSources() for a
description of |base_path_components|."""
string if the path shouldn't be considered. See _AddSources() for a
description of |base_path_components|."""
depth = 0
while path.startswith("../"):
depth += 1
@ -150,14 +150,14 @@ def _ResolveParent(path, base_path_components):
def _AddSources(sources, base_path, base_path_components, result):
"""Extracts valid sources from |sources| and adds them to |result|. Each
source file is relative to |base_path|, but may contain '..'. To make
resolving '..' easier |base_path_components| contains each of the
directories in |base_path|. Additionally each source may contain variables.
Such sources are ignored as it is assumed dependencies on them are expressed
and tracked in some other means."""
source file is relative to |base_path|, but may contain '..'. To make
resolving '..' easier |base_path_components| contains each of the
directories in |base_path|. Additionally each source may contain variables.
Such sources are ignored as it is assumed dependencies on them are expressed
and tracked in some other means."""
# NOTE: gyp paths are always posix style.
for source in sources:
if not len(source) or source.startswith("!!!") or source.startswith("$"):
if not len(source) or source.startswith(("!!!", "$")):
continue
# variable expansion may lead to //.
org_source = source
@ -217,23 +217,23 @@ def _ExtractSources(target, target_dict, toplevel_dir):
class Target:
"""Holds information about a particular target:
deps: set of Targets this Target depends upon. This is not recursive, only the
direct dependent Targets.
match_status: one of the MatchStatus values.
back_deps: set of Targets that have a dependency on this Target.
visited: used during iteration to indicate whether we've visited this target.
This is used for two iterations, once in building the set of Targets and
again in _GetBuildTargets().
name: fully qualified name of the target.
requires_build: True if the target type is such that it needs to be built.
See _DoesTargetTypeRequireBuild for details.
added_to_compile_targets: used when determining if the target was added to the
set of targets that needs to be built.
in_roots: true if this target is a descendant of one of the root nodes.
is_executable: true if the type of target is executable.
is_static_library: true if the type of target is static_library.
is_or_has_linked_ancestor: true if the target does a link (eg executable), or
if there is a target in back_deps that does a link."""
deps: set of Targets this Target depends upon. This is not recursive, only the
direct dependent Targets.
match_status: one of the MatchStatus values.
back_deps: set of Targets that have a dependency on this Target.
visited: used during iteration to indicate whether we've visited this target.
This is used for two iterations, once in building the set of Targets and
again in _GetBuildTargets().
name: fully qualified name of the target.
requires_build: True if the target type is such that it needs to be built.
See _DoesTargetTypeRequireBuild for details.
added_to_compile_targets: used when determining if the target was added to the
set of targets that needs to be built.
in_roots: true if this target is a descendant of one of the root nodes.
is_executable: true if the type of target is executable.
is_static_library: true if the type of target is static_library.
is_or_has_linked_ancestor: true if the target does a link (eg executable), or
if there is a target in back_deps that does a link."""
def __init__(self, name):
self.deps = set()
@ -253,8 +253,8 @@ class Target:
class Config:
"""Details what we're looking for
files: set of files to search for
targets: see file description for details."""
files: set of files to search for
targets: see file description for details."""
def __init__(self):
self.files = []
@ -264,7 +264,7 @@ class Config:
def Init(self, params):
"""Initializes Config. This is a separate method as it raises an exception
if there is a parse error."""
if there is a parse error."""
generator_flags = params.get("generator_flags", {})
config_path = generator_flags.get("config_path", None)
if not config_path:
@ -288,8 +288,8 @@ class Config:
def _WasBuildFileModified(build_file, data, files, toplevel_dir):
"""Returns true if the build file |build_file| is either in |files| or
one of the files included by |build_file| is in |files|. |toplevel_dir| is
the root of the source tree."""
one of the files included by |build_file| is in |files|. |toplevel_dir| is
the root of the source tree."""
if _ToLocalPath(toplevel_dir, _ToGypPath(build_file)) in files:
if debug:
print("gyp file modified", build_file)
@ -318,8 +318,8 @@ def _WasBuildFileModified(build_file, data, files, toplevel_dir):
def _GetOrCreateTargetByName(targets, target_name):
"""Creates or returns the Target at targets[target_name]. If there is no
Target for |target_name| one is created. Returns a tuple of whether a new
Target was created and the Target."""
Target for |target_name| one is created. Returns a tuple of whether a new
Target was created and the Target."""
if target_name in targets:
return False, targets[target_name]
target = Target(target_name)
@ -339,13 +339,13 @@ def _DoesTargetTypeRequireBuild(target_dict):
def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, build_files):
"""Returns a tuple of the following:
. A dictionary mapping from fully qualified name to Target.
. A list of the targets that have a source file in |files|.
. Targets that constitute the 'all' target. See description at top of file
for details on the 'all' target.
This sets the |match_status| of the targets that contain any of the source
files in |files| to MATCH_STATUS_MATCHES.
|toplevel_dir| is the root of the source tree."""
. A dictionary mapping from fully qualified name to Target.
. A list of the targets that have a source file in |files|.
. Targets that constitute the 'all' target. See description at top of file
for details on the 'all' target.
This sets the |match_status| of the targets that contain any of the source
files in |files| to MATCH_STATUS_MATCHES.
|toplevel_dir| is the root of the source tree."""
# Maps from target name to Target.
name_to_target = {}
@ -378,9 +378,10 @@ def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, build
target_type = target_dicts[target_name]["type"]
target.is_executable = target_type == "executable"
target.is_static_library = target_type == "static_library"
target.is_or_has_linked_ancestor = (
target_type == "executable" or target_type == "shared_library"
)
target.is_or_has_linked_ancestor = target_type in {
"executable",
"shared_library",
}
build_file = gyp.common.ParseQualifiedTarget(target_name)[0]
if build_file not in build_file_in_files:
@ -426,34 +427,34 @@ def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, build
def _GetUnqualifiedToTargetMapping(all_targets, to_find):
"""Returns a tuple of the following:
. mapping (dictionary) from unqualified name to Target for all the
Targets in |to_find|.
. any target names not found. If this is empty all targets were found."""
. mapping (dictionary) from unqualified name to Target for all the
Targets in |to_find|.
. any target names not found. If this is empty all targets were found."""
result = {}
if not to_find:
return {}, []
to_find = set(to_find)
for target_name in all_targets.keys():
for target_name in all_targets:
extracted = gyp.common.ParseQualifiedTarget(target_name)
if len(extracted) > 1 and extracted[1] in to_find:
to_find.remove(extracted[1])
result[extracted[1]] = all_targets[target_name]
if not to_find:
return result, []
return result, [x for x in to_find]
return result, list(to_find)
def _DoesTargetDependOnMatchingTargets(target):
"""Returns true if |target| or any of its dependencies is one of the
targets containing the files supplied as input to analyzer. This updates
|matches| of the Targets as it recurses.
target: the Target to look for."""
targets containing the files supplied as input to analyzer. This updates
|matches| of the Targets as it recurses.
target: the Target to look for."""
if target.match_status == MATCH_STATUS_DOESNT_MATCH:
return False
if (
target.match_status == MATCH_STATUS_MATCHES
or target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY
):
if target.match_status in {
MATCH_STATUS_MATCHES,
MATCH_STATUS_MATCHES_BY_DEPENDENCY,
}:
return True
for dep in target.deps:
if _DoesTargetDependOnMatchingTargets(dep):
@ -466,9 +467,9 @@ def _DoesTargetDependOnMatchingTargets(target):
def _GetTargetsDependingOnMatchingTargets(possible_targets):
"""Returns the list of Targets in |possible_targets| that depend (either
directly on indirectly) on at least one of the targets containing the files
supplied as input to analyzer.
possible_targets: targets to search from."""
directly on indirectly) on at least one of the targets containing the files
supplied as input to analyzer.
possible_targets: targets to search from."""
found = []
print("Targets that matched by dependency:")
for target in possible_targets:
@ -479,11 +480,11 @@ def _GetTargetsDependingOnMatchingTargets(possible_targets):
def _AddCompileTargets(target, roots, add_if_no_ancestor, result):
"""Recurses through all targets that depend on |target|, adding all targets
that need to be built (and are in |roots|) to |result|.
roots: set of root targets.
add_if_no_ancestor: If true and there are no ancestors of |target| then add
|target| to |result|. |target| must still be in |roots|.
result: targets that need to be built are added here."""
that need to be built (and are in |roots|) to |result|.
roots: set of root targets.
add_if_no_ancestor: If true and there are no ancestors of |target| then add
|target| to |result|. |target| must still be in |roots|.
result: targets that need to be built are added here."""
if target.visited:
return
@ -536,8 +537,8 @@ def _AddCompileTargets(target, roots, add_if_no_ancestor, result):
def _GetCompileTargets(matching_targets, supplied_targets):
"""Returns the set of Targets that require a build.
matching_targets: targets that changed and need to be built.
supplied_targets: set of targets supplied to analyzer to search from."""
matching_targets: targets that changed and need to be built.
supplied_targets: set of targets supplied to analyzer to search from."""
result = set()
for target in matching_targets:
print("finding compile targets for match", target.name)
@ -591,7 +592,7 @@ def _WriteOutput(params, **values):
def _WasGypIncludeFileModified(params, files):
"""Returns true if one of the files in |files| is in the set of included
files."""
files."""
if params["options"].includes:
for include in params["options"].includes:
if _ToGypPath(os.path.normpath(include)) in files:
@ -607,7 +608,7 @@ def _NamesNotIn(names, mapping):
def _LookupTargets(names, mapping):
"""Returns a list of the mapping[name] for each value in |names| that is in
|mapping|."""
|mapping|."""
return [mapping[name] for name in names if name in mapping]
@ -683,11 +684,9 @@ class TargetCalculator:
)
test_target_names_contains_all = "all" in self._test_target_names
if test_target_names_contains_all:
test_targets = [
x for x in (set(test_targets_no_all) | set(self._root_targets))
]
test_targets = list(set(test_targets_no_all) | set(self._root_targets))
else:
test_targets = [x for x in test_targets_no_all]
test_targets = list(test_targets_no_all)
print("supplied test_targets")
for target_name in self._test_target_names:
print("\t", target_name)
@ -701,10 +700,10 @@ class TargetCalculator:
) & set(self._root_targets)
if matching_test_targets_contains_all:
# Remove any of the targets for all that were not explicitly supplied,
# 'all' is subsequentely added to the matching names below.
matching_test_targets = [
x for x in (set(matching_test_targets) & set(test_targets_no_all))
]
# 'all' is subsequently added to the matching names below.
matching_test_targets = list(
set(matching_test_targets) & set(test_targets_no_all)
)
print("matched test_targets")
for target in matching_test_targets:
print("\t", target.name)
@ -729,9 +728,7 @@ class TargetCalculator:
self._supplied_target_names_no_all(), self._unqualified_mapping
)
if "all" in self._supplied_target_names():
supplied_targets = [
x for x in (set(supplied_targets) | set(self._root_targets))
]
supplied_targets = list(set(supplied_targets) | set(self._root_targets))
print("Supplied test_targets & compile_targets")
for target in supplied_targets:
print("\t", target.name)
@ -751,7 +748,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
if not config.files:
raise Exception(
"Must specify files to analyze via config_path generator " "flag"
"Must specify files to analyze via config_path generator flag"
)
toplevel_dir = _ToGypPath(os.path.abspath(params["options"].toplevel_dir))

View file

@ -15,13 +15,14 @@
# Try to avoid setting global variables where possible.
import gyp
import gyp.common
import gyp.generator.make as make # Reuse global functions from make backend.
import os
import re
import subprocess
import gyp
import gyp.common
from gyp.generator import make # Reuse global functions from make backend.
generator_default_variables = {
"OS": "android",
"EXECUTABLE_PREFIX": "",
@ -176,9 +177,7 @@ class AndroidMkWriter:
self.WriteLn("LOCAL_IS_HOST_MODULE := true")
self.WriteLn("LOCAL_MULTILIB := $(GYP_HOST_MULTILIB)")
elif sdk_version > 0:
self.WriteLn(
"LOCAL_MODULE_TARGET_ARCH := " "$(TARGET_$(GYP_VAR_PREFIX)ARCH)"
)
self.WriteLn("LOCAL_MODULE_TARGET_ARCH := $(TARGET_$(GYP_VAR_PREFIX)ARCH)")
self.WriteLn("LOCAL_SDK_VERSION := %s" % sdk_version)
# Grab output directories; needed for Actions and Rules.
@ -379,7 +378,7 @@ class AndroidMkWriter:
inputs = rule.get("inputs")
for rule_source in rule.get("rule_sources", []):
(rule_source_dirname, rule_source_basename) = os.path.split(rule_source)
(rule_source_root, rule_source_ext) = os.path.splitext(
(rule_source_root, _rule_source_ext) = os.path.splitext(
rule_source_basename
)
@ -587,11 +586,11 @@ class AndroidMkWriter:
local_files = []
for source in sources:
(root, ext) = os.path.splitext(source)
if "$(gyp_shared_intermediate_dir)" in source:
extra_sources.append(source)
elif "$(gyp_intermediate_dir)" in source:
extra_sources.append(source)
elif IsCPPExtension(ext) and ext != local_cpp_extension:
if (
"$(gyp_shared_intermediate_dir)" in source
or "$(gyp_intermediate_dir)" in source
or (IsCPPExtension(ext) and ext != local_cpp_extension)
):
extra_sources.append(source)
else:
local_files.append(os.path.normpath(os.path.join(self.path, source)))
@ -697,7 +696,7 @@ class AndroidMkWriter:
target,
)
if self.type != "static_library" and self.type != "shared_library":
if self.type not in {"static_library", "shared_library"}:
target_prefix = spec.get("product_prefix", target_prefix)
target = spec.get("product_name", target)
product_ext = spec.get("product_extension")
@ -730,19 +729,17 @@ class AndroidMkWriter:
path = "$($(GYP_HOST_VAR_PREFIX)HOST_OUT_INTERMEDIATE_LIBRARIES)"
else:
path = "$($(GYP_VAR_PREFIX)TARGET_OUT_INTERMEDIATE_LIBRARIES)"
# Other targets just get built into their intermediate dir.
elif self.toolset == "host":
path = (
"$(call intermediates-dir-for,%s,%s,true,,"
"$(GYP_HOST_VAR_PREFIX))" % (self.android_class, self.android_module)
)
else:
# Other targets just get built into their intermediate dir.
if self.toolset == "host":
path = (
"$(call intermediates-dir-for,%s,%s,true,,"
"$(GYP_HOST_VAR_PREFIX))"
% (self.android_class, self.android_module)
)
else:
path = "$(call intermediates-dir-for,{},{},,,$(GYP_VAR_PREFIX))".format(
self.android_class,
self.android_module,
)
path = (
f"$(call intermediates-dir-for,{self.android_class},"
f"{self.android_module},,,$(GYP_VAR_PREFIX))"
)
assert spec.get("product_dir") is None # TODO: not supported?
return os.path.join(path, self.ComputeOutputBasename(spec))
@ -769,7 +766,7 @@ class AndroidMkWriter:
Args:
cflags: A list of compiler flags, which may be mixed with "-I.."
Returns:
A tuple of lists: (clean_clfags, include_paths). "-I.." is trimmed.
A tuple of lists: (clean_cflags, include_paths). "-I.." is trimmed.
"""
clean_cflags = []
include_paths = []
@ -901,8 +898,7 @@ class AndroidMkWriter:
if self.type != "none":
self.WriteTargetFlags(spec, configs, link_deps)
settings = spec.get("aosp_build_settings", {})
if settings:
if settings := spec.get("aosp_build_settings", {}):
self.WriteLn("### Set directly by aosp_build_settings.")
for k, v in settings.items():
if isinstance(v, list):
@ -1003,9 +999,9 @@ class AndroidMkWriter:
# - i.e. that the resulting path is still inside the project tree. The
# path may legitimately have ended up containing just $(LOCAL_PATH), though,
# so we don't look for a slash.
assert local_path.startswith(
"$(LOCAL_PATH)"
), f"Path {path} attempts to escape from gyp path {self.path} !)"
assert local_path.startswith("$(LOCAL_PATH)"), (
f"Path {path} attempts to escape from gyp path {self.path} !)"
)
return local_path
def ExpandInputRoot(self, template, expansion, dirname):
@ -1047,9 +1043,9 @@ def GenerateOutput(target_list, target_dicts, data, params):
base_path = gyp.common.RelativePath(os.path.dirname(build_file), options.depth)
# We write the file in the base_path directory.
output_file = os.path.join(options.depth, base_path, base_name)
assert (
not options.generator_output
), "The Android backend does not support options.generator_output."
assert not options.generator_output, (
"The Android backend does not support options.generator_output."
)
base_path = gyp.common.RelativePath(
os.path.dirname(build_file), options.toplevel_dir
)
@ -1069,9 +1065,9 @@ def GenerateOutput(target_list, target_dicts, data, params):
makefile_name = "GypAndroid" + options.suffix + ".mk"
makefile_path = os.path.join(options.toplevel_dir, makefile_name)
assert (
not options.generator_output
), "The Android backend does not support options.generator_output."
assert not options.generator_output, (
"The Android backend does not support options.generator_output."
)
gyp.common.EnsureDirExists(makefile_path)
root_makefile = open(makefile_path, "w")

View file

@ -28,11 +28,11 @@ not be able to find the header file directories described in the generated
CMakeLists.txt file.
"""
import multiprocessing
import os
import signal
import subprocess
import gyp.common
import gyp.xcode_emulation
@ -96,14 +96,14 @@ def Linkable(filename):
def NormjoinPathForceCMakeSource(base_path, rel_path):
"""Resolves rel_path against base_path and returns the result.
If rel_path is an absolute path it is returned unchanged.
Otherwise it is resolved against base_path and normalized.
If the result is a relative path, it is forced to be relative to the
CMakeLists.txt.
"""
If rel_path is an absolute path it is returned unchanged.
Otherwise it is resolved against base_path and normalized.
If the result is a relative path, it is forced to be relative to the
CMakeLists.txt.
"""
if os.path.isabs(rel_path):
return rel_path
if any([rel_path.startswith(var) for var in FULL_PATH_VARS]):
if any(rel_path.startswith(var) for var in FULL_PATH_VARS):
return rel_path
# TODO: do we need to check base_path for absolute variables as well?
return os.path.join(
@ -113,10 +113,10 @@ def NormjoinPathForceCMakeSource(base_path, rel_path):
def NormjoinPath(base_path, rel_path):
"""Resolves rel_path against base_path and returns the result.
TODO: what is this really used for?
If rel_path begins with '$' it is returned unchanged.
Otherwise it is resolved against base_path if relative, then normalized.
"""
TODO: what is this really used for?
If rel_path begins with '$' it is returned unchanged.
Otherwise it is resolved against base_path if relative, then normalized.
"""
if rel_path.startswith("$") and not rel_path.startswith("${configuration}"):
return rel_path
return os.path.normpath(os.path.join(base_path, rel_path))
@ -125,19 +125,19 @@ def NormjoinPath(base_path, rel_path):
def CMakeStringEscape(a):
"""Escapes the string 'a' for use inside a CMake string.
This means escaping
'\' otherwise it may be seen as modifying the next character
'"' otherwise it will end the string
';' otherwise the string becomes a list
This means escaping
'\' otherwise it may be seen as modifying the next character
'"' otherwise it will end the string
';' otherwise the string becomes a list
The following do not need to be escaped
'#' when the lexer is in string state, this does not start a comment
The following do not need to be escaped
'#' when the lexer is in string state, this does not start a comment
The following are yet unknown
'$' generator variables (like ${obj}) must not be escaped,
but text $ should be escaped
what is wanted is to know which $ come from generator variables
"""
The following are yet unknown
'$' generator variables (like ${obj}) must not be escaped,
but text $ should be escaped
what is wanted is to know which $ come from generator variables
"""
return a.replace("\\", "\\\\").replace(";", "\\;").replace('"', '\\"')
@ -236,25 +236,25 @@ cmake_target_type_from_gyp_target_type = {
def StringToCMakeTargetName(a):
"""Converts the given string 'a' to a valid CMake target name.
All invalid characters are replaced by '_'.
Invalid for cmake: ' ', '/', '(', ')', '"'
Invalid for make: ':'
Invalid for unknown reasons but cause failures: '.'
"""
All invalid characters are replaced by '_'.
Invalid for cmake: ' ', '/', '(', ')', '"'
Invalid for make: ':'
Invalid for unknown reasons but cause failures: '.'
"""
return a.translate(_maketrans(' /():."', "_______"))
def WriteActions(target_name, actions, extra_sources, extra_deps, path_to_gyp, output):
"""Write CMake for the 'actions' in the target.
Args:
target_name: the name of the CMake target being generated.
actions: the Gyp 'actions' dict for this target.
extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
extra_deps: [<cmake_taget>] to append with generated targets.
path_to_gyp: relative path from CMakeLists.txt being generated to
the Gyp file in which the target being generated is defined.
"""
Args:
target_name: the name of the CMake target being generated.
actions: the Gyp 'actions' dict for this target.
extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
extra_deps: [<cmake_target>] to append with generated targets.
path_to_gyp: relative path from CMakeLists.txt being generated to
the Gyp file in which the target being generated is defined.
"""
for action in actions:
action_name = StringToCMakeTargetName(action["action_name"])
action_target_name = f"{target_name}__{action_name}"
@ -328,7 +328,7 @@ def WriteActions(target_name, actions, extra_sources, extra_deps, path_to_gyp, o
def NormjoinRulePathForceCMakeSource(base_path, rel_path, rule_source):
if rel_path.startswith(("${RULE_INPUT_PATH}", "${RULE_INPUT_DIRNAME}")):
if any([rule_source.startswith(var) for var in FULL_PATH_VARS]):
if any(rule_source.startswith(var) for var in FULL_PATH_VARS):
return rel_path
return NormjoinPathForceCMakeSource(base_path, rel_path)
@ -336,14 +336,14 @@ def NormjoinRulePathForceCMakeSource(base_path, rel_path, rule_source):
def WriteRules(target_name, rules, extra_sources, extra_deps, path_to_gyp, output):
"""Write CMake for the 'rules' in the target.
Args:
target_name: the name of the CMake target being generated.
actions: the Gyp 'actions' dict for this target.
extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
extra_deps: [<cmake_taget>] to append with generated targets.
path_to_gyp: relative path from CMakeLists.txt being generated to
the Gyp file in which the target being generated is defined.
"""
Args:
target_name: the name of the CMake target being generated.
actions: the Gyp 'actions' dict for this target.
extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
extra_deps: [<cmake_target>] to append with generated targets.
path_to_gyp: relative path from CMakeLists.txt being generated to
the Gyp file in which the target being generated is defined.
"""
for rule in rules:
rule_name = StringToCMakeTargetName(target_name + "__" + rule["rule_name"])
@ -454,13 +454,13 @@ def WriteRules(target_name, rules, extra_sources, extra_deps, path_to_gyp, outpu
def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output):
"""Write CMake for the 'copies' in the target.
Args:
target_name: the name of the CMake target being generated.
actions: the Gyp 'actions' dict for this target.
extra_deps: [<cmake_taget>] to append with generated targets.
path_to_gyp: relative path from CMakeLists.txt being generated to
the Gyp file in which the target being generated is defined.
"""
Args:
target_name: the name of the CMake target being generated.
actions: the Gyp 'actions' dict for this target.
extra_deps: [<cmake_target>] to append with generated targets.
path_to_gyp: relative path from CMakeLists.txt being generated to
the Gyp file in which the target being generated is defined.
"""
copy_name = target_name + "__copies"
# CMake gets upset with custom targets with OUTPUT which specify no output.
@ -584,26 +584,26 @@ def CreateCMakeTargetFullName(qualified_target):
class CMakeNamer:
"""Converts Gyp target names into CMake target names.
CMake requires that target names be globally unique. One way to ensure
this is to fully qualify the names of the targets. Unfortunately, this
ends up with all targets looking like "chrome_chrome_gyp_chrome" instead
of just "chrome". If this generator were only interested in building, it
would be possible to fully qualify all target names, then create
unqualified target names which depend on all qualified targets which
should have had that name. This is more or less what the 'make' generator
does with aliases. However, one goal of this generator is to create CMake
files for use with IDEs, and fully qualified names are not as user
friendly.
CMake requires that target names be globally unique. One way to ensure
this is to fully qualify the names of the targets. Unfortunately, this
ends up with all targets looking like "chrome_chrome_gyp_chrome" instead
of just "chrome". If this generator were only interested in building, it
would be possible to fully qualify all target names, then create
unqualified target names which depend on all qualified targets which
should have had that name. This is more or less what the 'make' generator
does with aliases. However, one goal of this generator is to create CMake
files for use with IDEs, and fully qualified names are not as user
friendly.
Since target name collision is rare, we do the above only when required.
Since target name collision is rare, we do the above only when required.
Toolset variants are always qualified from the base, as this is required for
building. However, it also makes sense for an IDE, as it is possible for
defines to be different.
"""
Toolset variants are always qualified from the base, as this is required for
building. However, it also makes sense for an IDE, as it is possible for
defines to be different.
"""
def __init__(self, target_list):
self.cmake_target_base_names_conficting = set()
self.cmake_target_base_names_conflicting = set()
cmake_target_base_names_seen = set()
for qualified_target in target_list:
@ -612,11 +612,11 @@ class CMakeNamer:
if cmake_target_base_name not in cmake_target_base_names_seen:
cmake_target_base_names_seen.add(cmake_target_base_name)
else:
self.cmake_target_base_names_conficting.add(cmake_target_base_name)
self.cmake_target_base_names_conflicting.add(cmake_target_base_name)
def CreateCMakeTargetName(self, qualified_target):
base_name = CreateCMakeTargetBaseName(qualified_target)
if base_name in self.cmake_target_base_names_conficting:
if base_name in self.cmake_target_base_names_conflicting:
return CreateCMakeTargetFullName(qualified_target)
return base_name
@ -809,8 +809,7 @@ def WriteTarget(
# link directories to targets defined after it is called.
# As a result, link_directories must come before the target definition.
# CMake unfortunately has no means of removing entries from LINK_DIRECTORIES.
library_dirs = config.get("library_dirs")
if library_dirs is not None:
if (library_dirs := config.get("library_dirs")) is not None:
output.write("link_directories(")
for library_dir in library_dirs:
output.write(" ")
@ -929,10 +928,7 @@ def WriteTarget(
product_prefix = spec.get("product_prefix", default_product_prefix)
product_name = spec.get("product_name", default_product_name)
product_ext = spec.get("product_extension")
if product_ext:
product_ext = "." + product_ext
else:
product_ext = default_product_ext
product_ext = "." + product_ext if product_ext else default_product_ext
SetTargetProperty(output, cmake_target_name, "PREFIX", product_prefix)
SetTargetProperty(
@ -1297,8 +1293,7 @@ def CallGenerateOutputForConfig(arglist):
def GenerateOutput(target_list, target_dicts, data, params):
user_config = params.get("generator_flags", {}).get("config", None)
if user_config:
if user_config := params.get("generator_flags", {}).get("config", None):
GenerateOutputForConfig(target_list, target_dicts, data, params, user_config)
else:
config_names = target_dicts[target_list[0]]["configurations"]

View file

@ -2,11 +2,12 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import gyp.common
import gyp.xcode_emulation
import json
import os
import gyp.common
import gyp.xcode_emulation
generator_additional_non_configuration_keys = []
generator_additional_path_sections = []
generator_extra_sources_for_rules = []
@ -34,7 +35,7 @@ generator_default_variables = {
def IsMac(params):
return "mac" == gyp.common.GetFlavor(params)
return gyp.common.GetFlavor(params) == "mac"
def CalculateVariables(default_variables, params):
@ -93,13 +94,13 @@ def AddCommandsForTarget(cwd, target, params, per_config_commands):
gyp.common.EncodePOSIXShellArgument(file),
)
)
commands.append(dict(command=command, directory=output_dir, file=file))
commands.append({"command": command, "directory": output_dir, "file": file})
def GenerateOutput(target_list, target_dicts, data, params):
per_config_commands = {}
for qualified_target, target in target_dicts.items():
build_file, target_name, toolset = gyp.common.ParseQualifiedTarget(
build_file, _target_name, _toolset = gyp.common.ParseQualifiedTarget(
qualified_target
)
if IsMac(params):
@ -108,7 +109,14 @@ def GenerateOutput(target_list, target_dicts, data, params):
cwd = os.path.dirname(build_file)
AddCommandsForTarget(cwd, target, params, per_config_commands)
output_dir = params["generator_flags"].get("output_dir", "out")
output_dir = None
try:
# generator_output can be `None` on Windows machines, or even not
# defined in other cases
output_dir = params.get("options").generator_output
except AttributeError:
pass
output_dir = output_dir or params["generator_flags"].get("output_dir", "out")
for configuration_name, commands in per_config_commands.items():
filename = os.path.join(output_dir, configuration_name, "compile_commands.json")
gyp.common.EnsureDirExists(filename)

View file

@ -3,11 +3,12 @@
# found in the LICENSE file.
import json
import os
import gyp
import gyp.common
import gyp.msvs_emulation
import json
generator_supports_multiple_toolsets = True
@ -55,7 +56,7 @@ def CalculateVariables(default_variables, params):
def CalculateGeneratorInputInfo(params):
"""Calculate the generator specific info that gets fed to input (called by
gyp)."""
gyp)."""
generator_flags = params.get("generator_flags", {})
if generator_flags.get("adjust_static_libraries", False):
global generator_wants_static_library_dependencies_adjusted

View file

@ -17,14 +17,15 @@ still result in a few indexer issues here and there.
This generator has no automated tests, so expect it to be broken.
"""
from xml.sax.saxutils import escape
import os.path
import shlex
import subprocess
import xml.etree.ElementTree as ET
from xml.sax.saxutils import escape
import gyp
import gyp.common
import gyp.msvs_emulation
import shlex
import xml.etree.ElementTree as ET
generator_wants_static_library_dependencies_adjusted = False
@ -68,7 +69,7 @@ def CalculateVariables(default_variables, params):
def CalculateGeneratorInputInfo(params):
"""Calculate the generator specific info that gets fed to input (called by
gyp)."""
gyp)."""
generator_flags = params.get("generator_flags", {})
if generator_flags.get("adjust_static_libraries", False):
global generator_wants_static_library_dependencies_adjusted
@ -85,10 +86,10 @@ def GetAllIncludeDirectories(
):
"""Calculate the set of include directories to be used.
Returns:
A list including all the include_dir's specified for every target followed
by any include directories that were added as cflag compiler options.
"""
Returns:
A list including all the include_dir's specified for every target followed
by any include directories that were added as cflag compiler options.
"""
gyp_includes_set = set()
compiler_includes_list = []
@ -177,11 +178,11 @@ def GetAllIncludeDirectories(
def GetCompilerPath(target_list, data, options):
"""Determine a command that can be used to invoke the compiler.
Returns:
If this is a gyp project that has explicit make settings, try to determine
the compiler from that. Otherwise, see if a compiler was specified via the
CC_target environment variable.
"""
Returns:
If this is a gyp project that has explicit make settings, try to determine
the compiler from that. Otherwise, see if a compiler was specified via the
CC_target environment variable.
"""
# First, see if the compiler is configured in make's settings.
build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0])
make_global_settings_dict = data[build_file].get("make_global_settings", {})
@ -201,10 +202,10 @@ def GetCompilerPath(target_list, data, options):
def GetAllDefines(target_list, target_dicts, data, config_name, params, compiler_path):
"""Calculate the defines for a project.
Returns:
A dict that includes explicit defines declared in gyp files along with all
of the default defines that the compiler uses.
"""
Returns:
A dict that includes explicit defines declared in gyp files along with all
of the default defines that the compiler uses.
"""
# Get defines declared in the gyp files.
all_defines = {}
@ -248,10 +249,7 @@ def GetAllDefines(target_list, target_dicts, data, config_name, params, compiler
continue
cpp_line_parts = cpp_line.split(" ", 2)
key = cpp_line_parts[1]
if len(cpp_line_parts) >= 3:
val = cpp_line_parts[2]
else:
val = "1"
val = cpp_line_parts[2] if len(cpp_line_parts) >= 3 else "1"
all_defines[key] = val
return all_defines
@ -375,8 +373,8 @@ def GenerateClasspathFile(
target_list, target_dicts, toplevel_dir, toplevel_build, out_name
):
"""Generates a classpath file suitable for symbol navigation and code
completion of Java code (such as in Android projects) by finding all
.java and .jar files used as action inputs."""
completion of Java code (such as in Android projects) by finding all
.java and .jar files used as action inputs."""
gyp.common.EnsureDirExists(out_name)
result = ET.Element("classpath")
@ -453,8 +451,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
if params["options"].generator_output:
raise NotImplementedError("--generator_output not implemented for eclipse")
user_config = params.get("generator_flags", {}).get("config", None)
if user_config:
if user_config := params.get("generator_flags", {}).get("config", None):
GenerateOutputForConfig(target_list, target_dicts, data, params, user_config)
else:
config_names = target_dicts[target_list[0]]["configurations"]

View file

@ -30,10 +30,9 @@ The specific formatting of the output generated by this module is subject
to change.
"""
import gyp.common
import pprint
import gyp.common
# These variables should just be spit back out as variable references.
_generator_identity_variables = [
@ -74,7 +73,7 @@ for v in _generator_identity_variables:
def GenerateOutput(target_list, target_dicts, data, params):
output_files = {}
for qualified_target in target_list:
[input_file, target] = gyp.common.ParseQualifiedTarget(qualified_target)[0:2]
[input_file, _target] = gyp.common.ParseQualifiedTarget(qualified_target)[0:2]
if input_file[-4:] != ".gyp":
continue

View file

@ -13,11 +13,9 @@ by the input module.
The expected usage is "gyp -f gypsh -D OS=desired_os".
"""
import code
import sys
# All of this stuff about generator variables was lovingly ripped from gypd.py.
# That module has a much better description of what's going on and why.
_generator_identity_variables = [
@ -49,10 +47,9 @@ def GenerateOutput(target_list, target_dicts, data, params):
# Use a banner that looks like the stock Python one and like what
# code.interact uses by default, but tack on something to indicate what
# locals are available, and identify gypsh.
banner = "Python {} on {}\nlocals.keys() = {}\ngypsh".format(
sys.version,
sys.platform,
repr(sorted(locals.keys())),
banner = (
f"Python {sys.version} on {sys.platform}\nlocals.keys() = "
f"{sorted(locals.keys())!r}\ngypsh"
)
code.interact(banner, local=locals)

View file

@ -22,16 +22,17 @@
# the side to keep the files readable.
import hashlib
import os
import re
import subprocess
import sys
import gyp
import gyp.common
import gyp.xcode_emulation
from gyp.common import GetEnvironFallback
import hashlib
generator_default_variables = {
"EXECUTABLE_PREFIX": "",
"EXECUTABLE_SUFFIX": "",
@ -77,7 +78,7 @@ def CalculateVariables(default_variables, params):
# Copy additional generator configuration data from Xcode, which is shared
# by the Mac Make generator.
import gyp.generator.xcode as xcode_generator
import gyp.generator.xcode as xcode_generator # noqa: PLC0415
global generator_additional_non_configuration_keys
generator_additional_non_configuration_keys = getattr(
@ -207,7 +208,7 @@ cmd_solink_module = $(LINK.$(TOOLSET)) -o $@ -shared $(GYP_LDFLAGS) $(LDFLAGS.$(
LINK_COMMANDS_MAC = """\
quiet_cmd_alink = LIBTOOL-STATIC $@
cmd_alink = rm -f $@ && ./gyp-mac-tool filter-libtool libtool $(GYP_LIBTOOLFLAGS) -static -o $@ $(filter %.o,$^)
cmd_alink = rm -f $@ && %(python)s gyp-mac-tool filter-libtool libtool $(GYP_LIBTOOLFLAGS) -static -o $@ $(filter %%.o,$^)
quiet_cmd_link = LINK($(TOOLSET)) $@
cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o "$@" $(LD_INPUTS) $(LIBS)
@ -217,7 +218,7 @@ cmd_solink = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o
quiet_cmd_solink_module = SOLINK_MODULE($(TOOLSET)) $@
cmd_solink_module = $(LINK.$(TOOLSET)) -bundle $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(filter-out FORCE_DO_CMD, $^) $(LIBS)
""" # noqa: E501
""" % {"python": sys.executable} # noqa: E501
LINK_COMMANDS_ANDROID = """\
quiet_cmd_alink = AR($(TOOLSET)) $@
@ -378,7 +379,7 @@ CXX.target ?= %(CXX.target)s
CXXFLAGS.target ?= $(CPPFLAGS) $(CXXFLAGS)
LINK.target ?= %(LINK.target)s
LDFLAGS.target ?= $(LDFLAGS)
AR.target ?= $(AR)
AR.target ?= %(AR.target)s
PLI.target ?= %(PLI.target)s
# C++ apps need to be linked with g++.
@ -442,13 +443,27 @@ DEPFLAGS = %(makedep_args)s -MF $(depfile).raw
define fixup_dep
# The depfile may not exist if the input file didn't have any #includes.
touch $(depfile).raw
# Fixup path as in (1).
sed -e "s|^$(notdir $@)|$@|" $(depfile).raw >> $(depfile)
# Fixup path as in (1)."""
+ (
r"""
sed -e "s|^$(notdir $@)|$@|" -re 's/\\\\([^$$])/\/\1/g' $(depfile).raw >> $(depfile)"""
if sys.platform == "win32"
else r"""
sed -e "s|^$(notdir $@)|$@|" $(depfile).raw >> $(depfile)"""
)
+ r"""
# Add extra rules as in (2).
# We remove slashes and replace spaces with new lines;
# remove blank lines;
# delete the first line and append a colon to the remaining lines.
sed -e 's|\\||' -e 'y| |\n|' $(depfile).raw |\
# delete the first line and append a colon to the remaining lines."""
+ (
"""
sed -e 's/\\\\\\\\$$//' -e 's/\\\\\\\\/\\//g' -e 'y| |\\n|' $(depfile).raw |\\"""
if sys.platform == "win32"
else """
sed -e 's|\\\\||' -e 'y| |\\n|' $(depfile).raw |\\"""
)
+ r"""
grep -v '^$$' |\
sed -e 1d -e 's|$$|:|' \
>> $(depfile)
@ -600,14 +615,14 @@ cmd_pch_mm = $(CC.$(TOOLSET)) $(GYP_PCH_OBJCXXFLAGS) $(DEPFLAGS) -c -o $@ $<
# Use $(4) for the command, since $(2) and $(3) are used as flag by do_cmd
# already.
quiet_cmd_mac_tool = MACTOOL $(4) $<
cmd_mac_tool = ./gyp-mac-tool $(4) $< "$@"
cmd_mac_tool = %(python)s gyp-mac-tool $(4) $< "$@"
quiet_cmd_mac_package_framework = PACKAGE FRAMEWORK $@
cmd_mac_package_framework = ./gyp-mac-tool package-framework "$@" $(4)
cmd_mac_package_framework = %(python)s gyp-mac-tool package-framework "$@" $(4)
quiet_cmd_infoplist = INFOPLIST $@
cmd_infoplist = $(CC.$(TOOLSET)) -E -P -Wno-trigraphs -x c $(INFOPLIST_DEFINES) "$<" -o "$@"
""" # noqa: E501
""" % {"python": sys.executable} # noqa: E501
def WriteRootHeaderSuffixRules(writer):
@ -681,10 +696,7 @@ COMPILABLE_EXTENSIONS = {
def Compilable(filename):
"""Return true if the file is compilable (should be in OBJS)."""
for res in (filename.endswith(e) for e in COMPILABLE_EXTENSIONS):
if res:
return True
return False
return any(res for res in (filename.endswith(e) for e in COMPILABLE_EXTENSIONS))
def Linkable(filename):
@ -728,6 +740,12 @@ def QuoteIfNecessary(string):
return string
def replace_sep(string):
if sys.platform == "win32":
string = string.replace("\\\\", "/").replace("\\", "/")
return string
def StringToMakefileVariable(string):
"""Convert a string to a value that is acceptable as a make variable name."""
return re.sub("[^a-zA-Z0-9_]", "_", string)
@ -778,7 +796,7 @@ class MakefileWriter:
self.suffix_rules_objdir2 = {}
# Generate suffix rules for all compilable extensions.
for ext in COMPILABLE_EXTENSIONS.keys():
for ext, value in COMPILABLE_EXTENSIONS.items():
# Suffix rules for source folder.
self.suffix_rules_srcdir.update(
{
@ -787,7 +805,7 @@ class MakefileWriter:
$(obj).$(TOOLSET)/$(TARGET)/%%.o: $(srcdir)/%%%s FORCE_DO_CMD
\t@$(call do_cmd,%s,1)
"""
% (ext, COMPILABLE_EXTENSIONS[ext])
% (ext, value)
)
}
)
@ -800,7 +818,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(srcdir)/%%%s FORCE_DO_CMD
$(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj).$(TOOLSET)/%%%s FORCE_DO_CMD
\t@$(call do_cmd,%s,1)
"""
% (ext, COMPILABLE_EXTENSIONS[ext])
% (ext, value)
)
}
)
@ -811,7 +829,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj).$(TOOLSET)/%%%s FORCE_DO_CMD
$(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
\t@$(call do_cmd,%s,1)
"""
% (ext, COMPILABLE_EXTENSIONS[ext])
% (ext, value)
)
}
)
@ -862,7 +880,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
self.output = self.ComputeMacBundleOutput(spec)
self.output_binary = self.ComputeMacBundleBinaryOutput(spec)
else:
self.output = self.output_binary = self.ComputeOutput(spec)
self.output = self.output_binary = replace_sep(self.ComputeOutput(spec))
self.is_standalone_static_library = bool(
spec.get("standalone_static_library", 0)
@ -988,7 +1006,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
# sub-project dir (see test/subdirectory/gyptest-subdir-all.py).
self.WriteLn(
"export builddir_name ?= %s"
% os.path.join(os.path.dirname(output_filename), build_dir)
% replace_sep(os.path.join(os.path.dirname(output_filename), build_dir))
)
self.WriteLn(".PHONY: all")
self.WriteLn("all:")
@ -1066,7 +1084,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
# libraries, but until everything is made cross-compile safe, also use
# target libraries.
# TODO(piman): when everything is cross-compile safe, remove lib.target
if self.flavor == "zos" or self.flavor == "aix":
if self.flavor in {"zos", "aix"}:
self.WriteLn(
"cmd_%s = LIBPATH=$(builddir)/lib.host:"
"$(builddir)/lib.target:$$LIBPATH; "
@ -1151,7 +1169,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
for rule_source in rule.get("rule_sources", []):
dirs = set()
(rule_source_dirname, rule_source_basename) = os.path.split(rule_source)
(rule_source_root, rule_source_ext) = os.path.splitext(
(rule_source_root, _rule_source_ext) = os.path.splitext(
rule_source_basename
)
@ -1429,9 +1447,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
for obj in objs:
assert " " not in obj, "Spaces in object filenames not supported (%s)" % obj
self.WriteLn(
"# Add to the list of files we specially track " "dependencies for."
)
self.WriteLn("# Add to the list of files we specially track dependencies for.")
self.WriteLn("all_deps += $(OBJS)")
self.WriteLn()
@ -1440,7 +1456,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
self.WriteMakeRule(
["$(OBJS)"],
deps,
comment="Make sure our dependencies are built " "before any of us.",
comment="Make sure our dependencies are built before any of us.",
order_only=True,
)
@ -1451,12 +1467,11 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
self.WriteMakeRule(
["$(OBJS)"],
extra_outputs,
comment="Make sure our actions/rules run " "before any of us.",
comment="Make sure our actions/rules run before any of us.",
order_only=True,
)
pchdeps = precompiled_header.GetObjDependencies(compilable, objs)
if pchdeps:
if pchdeps := precompiled_header.GetObjDependencies(compilable, objs):
self.WriteLn("# Dependencies from obj files to their precompiled headers")
for source, obj, gch in pchdeps:
self.WriteLn(f"{obj}: {gch}")
@ -1489,7 +1504,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
"$(OBJS): GYP_OBJCFLAGS := "
"$(DEFS_$(BUILDTYPE)) "
"$(INCS_$(BUILDTYPE)) "
"%s " % precompiled_header.GetInclude("m")
"%s "
% precompiled_header.GetInclude("m")
+ "$(CFLAGS_$(BUILDTYPE)) "
"$(CFLAGS_C_$(BUILDTYPE)) "
"$(CFLAGS_OBJC_$(BUILDTYPE))"
@ -1498,7 +1514,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
"$(OBJS): GYP_OBJCXXFLAGS := "
"$(DEFS_$(BUILDTYPE)) "
"$(INCS_$(BUILDTYPE)) "
"%s " % precompiled_header.GetInclude("mm")
"%s "
% precompiled_header.GetInclude("mm")
+ "$(CFLAGS_$(BUILDTYPE)) "
"$(CFLAGS_CC_$(BUILDTYPE)) "
"$(CFLAGS_OBJCC_$(BUILDTYPE))"
@ -1590,8 +1607,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
target_prefix = spec.get("product_prefix", target_prefix)
target = spec.get("product_name", target)
product_ext = spec.get("product_extension")
if product_ext:
if product_ext := spec.get("product_extension"):
target_ext = "." + product_ext
return target_prefix + target + target_ext
@ -1689,7 +1705,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
self.WriteMakeRule(
extra_outputs,
deps,
comment=("Preserve order dependency of " "special output on deps."),
comment=("Preserve order dependency of special output on deps."),
order_only=True,
)
@ -1728,7 +1744,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
# into the link command, so we need lots of escaping.
ldflags.append(r"-Wl,-rpath=\$$ORIGIN/")
ldflags.append(r"-Wl,-rpath-link=\$(builddir)/")
library_dirs = config.get("library_dirs", [])
if library_dirs := config.get("library_dirs", []):
library_dirs = [Sourceify(self.Absolutify(i)) for i in library_dirs]
ldflags += [("-L%s" % library_dir) for library_dir in library_dirs]
self.WriteList(ldflags, "LDFLAGS_%s" % configname)
if self.flavor == "mac":
@ -1769,13 +1786,13 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
# using ":=".
self.WriteSortedXcodeEnv(self.output, self.GetSortedXcodePostbuildEnv())
for configname in target_postbuilds:
for configname, value in target_postbuilds.items():
self.WriteLn(
"%s: TARGET_POSTBUILDS_%s := %s"
% (
QuoteSpaces(self.output),
configname,
gyp.common.EncodePOSIXShellList(target_postbuilds[configname]),
gyp.common.EncodePOSIXShellList(value),
)
)
@ -1824,7 +1841,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
# Since this target depends on binary and resources which are in
# nested subfolders, the framework directory will be older than
# its dependencies usually. To prevent this rule from executing
# on every build (expensive, especially with postbuilds), expliclity
# on every build (expensive, especially with postbuilds), explicitly
# update the time on the framework directory.
self.WriteLn("\t@touch -c %s" % QuoteSpaces(self.output))
@ -1834,7 +1851,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
"on the bundle, not the binary (target '%s')" % self.target
)
assert "product_dir" not in spec, (
"Postbuilds do not work with " "custom product_dir"
"Postbuilds do not work with custom product_dir"
)
if self.type == "executable":
@ -1871,7 +1888,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
self.flavor not in ("mac", "openbsd", "netbsd", "win")
and not self.is_standalone_static_library
):
if self.flavor in ("linux", "android"):
if self.flavor in ("linux", "android", "openharmony"):
self.WriteMakeRule(
[self.output_binary],
link_deps,
@ -1885,21 +1902,20 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
part_of_all,
postbuilds=postbuilds,
)
elif self.flavor in ("linux", "android", "openharmony"):
self.WriteMakeRule(
[self.output_binary],
link_deps,
actions=["$(call create_archive,$@,$^)"],
)
else:
if self.flavor in ("linux", "android"):
self.WriteMakeRule(
[self.output_binary],
link_deps,
actions=["$(call create_archive,$@,$^)"],
)
else:
self.WriteDoCmd(
[self.output_binary],
link_deps,
"alink",
part_of_all,
postbuilds=postbuilds,
)
self.WriteDoCmd(
[self.output_binary],
link_deps,
"alink",
part_of_all,
postbuilds=postbuilds,
)
elif self.type == "shared_library":
self.WriteLn(
"%s: LD_INPUTS := %s"
@ -1991,7 +2007,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
and "product_dir" not in spec
and self.toolset == "target"
):
# On macOS, products are created in install_path immediately.
# On mac, products are created in install_path immediately.
assert install_path == self.output, f"{install_path} != {self.output}"
# Point the target alias to the final binary output.
@ -2031,7 +2047,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
installable_deps.append(
self.GetUnversionedSidedeckFromSidedeck(install_path)
)
if self.output != self.alias and self.alias != self.target:
if self.alias not in (self.output, self.target):
self.WriteMakeRule(
[self.alias],
installable_deps,
@ -2066,7 +2082,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
"""
values = ""
if value_list:
value_list = [quoter(prefix + value) for value in value_list]
value_list = [replace_sep(quoter(prefix + value)) for value in value_list]
values = " \\\n\t" + " \\\n\t".join(value_list)
self.fp.write(f"{variable} :={values}\n\n")
@ -2153,7 +2169,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
# - The multi-output rule will have an do-nothing recipe.
# Hash the target name to avoid generating overlong filenames.
cmddigest = hashlib.sha1(
cmddigest = hashlib.sha256(
(command or self.target).encode("utf-8")
).hexdigest()
intermediate = "%s.intermediate" % cmddigest
@ -2372,9 +2388,15 @@ def WriteAutoRegenerationRule(params, root_makefile, makefile_name, build_files)
"\t$(call do_cmd,regen_makefile)\n\n"
% {
"makefile_name": makefile_name,
"deps": " ".join(SourceifyAndQuoteSpaces(bf) for bf in build_files),
"cmd": gyp.common.EncodePOSIXShellList(
[gyp_binary, "-fmake"] + gyp.RegenerateFlags(options) + build_files_args
"deps": replace_sep(
" ".join(sorted(SourceifyAndQuoteSpaces(bf) for bf in build_files))
),
"cmd": replace_sep(
gyp.common.EncodePOSIXShellList(
[gyp_binary, "-fmake"]
+ gyp.RegenerateFlags(options)
+ build_files_args
)
),
}
)
@ -2438,36 +2460,55 @@ def GenerateOutput(target_list, target_dicts, data, params):
makefile_path = os.path.join(
options.toplevel_dir, options.generator_output, makefile_name
)
srcdir = gyp.common.RelativePath(srcdir, options.generator_output)
srcdir = replace_sep(gyp.common.RelativePath(srcdir, options.generator_output))
srcdir_prefix = "$(srcdir)/"
flock_command = "flock"
copy_archive_arguments = "-af"
makedep_arguments = "-MMD"
# wasm-ld doesn't support --start-group/--end-group
link_commands = LINK_COMMANDS_LINUX
if flavor in ["wasi", "wasm"]:
link_commands = link_commands.replace(" -Wl,--start-group", "").replace(
" -Wl,--end-group", ""
)
CC_target = replace_sep(GetEnvironFallback(("CC_target", "CC"), "$(CC)"))
AR_target = replace_sep(GetEnvironFallback(("AR_target", "AR"), "$(AR)"))
CXX_target = replace_sep(GetEnvironFallback(("CXX_target", "CXX"), "$(CXX)"))
LINK_target = replace_sep(GetEnvironFallback(("LINK_target", "LINK"), "$(LINK)"))
PLI_target = replace_sep(GetEnvironFallback(("PLI_target", "PLI"), "pli"))
CC_host = replace_sep(GetEnvironFallback(("CC_host", "CC"), "gcc"))
AR_host = replace_sep(GetEnvironFallback(("AR_host", "AR"), "ar"))
CXX_host = replace_sep(GetEnvironFallback(("CXX_host", "CXX"), "g++"))
LINK_host = replace_sep(GetEnvironFallback(("LINK_host", "LINK"), "$(CXX.host)"))
PLI_host = replace_sep(GetEnvironFallback(("PLI_host", "PLI"), "pli"))
header_params = {
"default_target": default_target,
"builddir": builddir_name,
"default_configuration": default_configuration,
"flock": flock_command,
"flock_index": 1,
"link_commands": LINK_COMMANDS_LINUX,
"link_commands": link_commands,
"extra_commands": "",
"srcdir": srcdir,
"copy_archive_args": copy_archive_arguments,
"makedep_args": makedep_arguments,
"CC.target": GetEnvironFallback(("CC_target", "CC"), "$(CC)"),
"AR.target": GetEnvironFallback(("AR_target", "AR"), "$(AR)"),
"CXX.target": GetEnvironFallback(("CXX_target", "CXX"), "$(CXX)"),
"LINK.target": GetEnvironFallback(("LINK_target", "LINK"), "$(LINK)"),
"PLI.target": GetEnvironFallback(("PLI_target", "PLI"), "pli"),
"CC.host": GetEnvironFallback(("CC_host", "CC"), "gcc"),
"AR.host": GetEnvironFallback(("AR_host", "AR"), "ar"),
"CXX.host": GetEnvironFallback(("CXX_host", "CXX"), "g++"),
"LINK.host": GetEnvironFallback(("LINK_host", "LINK"), "$(CXX.host)"),
"PLI.host": GetEnvironFallback(("PLI_host", "PLI"), "pli"),
"CC.target": CC_target,
"AR.target": AR_target,
"CXX.target": CXX_target,
"LINK.target": LINK_target,
"PLI.target": PLI_target,
"CC.host": CC_host,
"AR.host": AR_host,
"CXX.host": CXX_host,
"LINK.host": LINK_host,
"PLI.host": PLI_host,
}
if flavor == "mac":
flock_command = "./gyp-mac-tool flock"
flock_command = "%s gyp-mac-tool flock" % sys.executable
header_params.update(
{
"flock": flock_command,
@ -2517,7 +2558,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
header_params.update(
{
"copy_archive_args": copy_archive_arguments,
"flock": "./gyp-flock-tool flock",
"flock": "%s gyp-flock-tool flock" % sys.executable,
"flock_index": 2,
}
)
@ -2533,7 +2574,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
{
"copy_archive_args": copy_archive_arguments,
"link_commands": LINK_COMMANDS_AIX,
"flock": "./gyp-flock-tool flock",
"flock": "%s gyp-flock-tool flock" % sys.executable,
"flock_index": 2,
}
)
@ -2543,7 +2584,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
{
"copy_archive_args": copy_archive_arguments,
"link_commands": LINK_COMMANDS_OS400,
"flock": "./gyp-flock-tool flock",
"flock": "%s gyp-flock-tool flock" % sys.executable,
"flock_index": 2,
}
)

File diff suppressed because it is too large Load diff

View file

@ -3,13 +3,13 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Unit tests for the msvs.py file. """
"""Unit tests for the msvs.py file."""
import gyp.generator.msvs as msvs
import unittest
from io import StringIO
from gyp.generator import msvs
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):

View file

@ -5,24 +5,24 @@
import collections
import copy
import ctypes
import hashlib
import json
import multiprocessing
import os.path
import re
import shutil
import signal
import subprocess
import sys
from io import StringIO
import gyp
import gyp.common
import gyp.msvs_emulation
import gyp.MSVSUtil as MSVSUtil
import gyp.xcode_emulation
from io import StringIO
from gyp import MSVSUtil, ninja_syntax
from gyp.common import GetEnvironFallback
import gyp.ninja_syntax as ninja_syntax
generator_default_variables = {
"EXECUTABLE_PREFIX": "",
@ -246,7 +246,7 @@ class NinjaWriter:
if flavor == "win":
# See docstring of msvs_emulation.GenerateEnvironmentFiles().
self.win_env = {}
for arch in ("x86", "x64"):
for arch in ("x86", "x64", "arm64"):
self.win_env[arch] = "environment." + arch
# Relative path from build output dir to base dir.
@ -264,8 +264,7 @@ class NinjaWriter:
dir.
"""
PRODUCT_DIR = "$!PRODUCT_DIR"
if PRODUCT_DIR in path:
if (PRODUCT_DIR := "$!PRODUCT_DIR") in path:
if product_dir:
path = path.replace(PRODUCT_DIR, product_dir)
else:
@ -273,8 +272,7 @@ class NinjaWriter:
path = path.replace(PRODUCT_DIR + "\\", "")
path = path.replace(PRODUCT_DIR, ".")
INTERMEDIATE_DIR = "$!INTERMEDIATE_DIR"
if INTERMEDIATE_DIR in path:
if (INTERMEDIATE_DIR := "$!INTERMEDIATE_DIR") in path:
int_dir = self.GypPathToUniqueOutput("gen")
# GypPathToUniqueOutput generates a path relative to the product dir,
# so insert product_dir in front if it is provided.
@ -811,9 +809,8 @@ class NinjaWriter:
outputs = [self.GypPathToNinja(o, env) for o in outputs]
if self.flavor == "win":
# WriteNewNinjaRule uses unique_name to create a rsp file on win.
extra_bindings.append(
("unique_name", hashlib.md5(outputs[0]).hexdigest())
)
unique_name = hashlib.sha256(outputs[0].encode("utf-8")).hexdigest()
extra_bindings.append(("unique_name", unique_name))
self.ninja.build(
outputs,
@ -1305,7 +1302,7 @@ class NinjaWriter:
ninja_file.build(gch, cmd, input, variables=[(var_name, lang_flag)])
def WriteLink(self, spec, config_name, config, link_deps, compile_deps):
"""Write out a link step. Fills out target.binary. """
"""Write out a link step. Fills out target.binary."""
if self.flavor != "mac" or len(self.archs) == 1:
return self.WriteLinkForArch(
self.ninja, spec, config_name, config, link_deps, compile_deps
@ -1349,7 +1346,7 @@ class NinjaWriter:
def WriteLinkForArch(
self, ninja_file, spec, config_name, config, link_deps, compile_deps, arch=None
):
"""Write out a link step. Fills out target.binary. """
"""Write out a link step. Fills out target.binary."""
command = {
"executable": "link",
"loadable_module": "solink_module",
@ -1464,7 +1461,7 @@ class NinjaWriter:
# Respect environment variables related to build, but target-specific
# flags can still override them.
ldflags = env_ldflags + config.get("ldflags", [])
if is_executable and len(solibs):
if is_executable and solibs:
rpath = "lib/"
if self.toolset != "target":
rpath += self.toolset
@ -1554,7 +1551,7 @@ class NinjaWriter:
if pdbname:
output = [output, pdbname]
if len(solibs):
if solibs:
extra_bindings.append(
("solibs", gyp.common.EncodePOSIXShellList(sorted(solibs)))
)
@ -1757,11 +1754,9 @@ class NinjaWriter:
+ " && ".join([ninja_syntax.escape(command) for command in postbuilds])
)
command_string = (
commands
+ "); G=$$?; "
commands + "); G=$$?; "
# Remove the final output if any postbuild failed.
"((exit $$G) || rm -rf %s) " % output
+ "&& exit $$G)"
"((exit $$G) || rm -rf %s) " % output + "&& exit $$G)"
)
if is_command_start:
return "(" + command_string + " && "
@ -1815,10 +1810,7 @@ class NinjaWriter:
"executable": default_variables["EXECUTABLE_SUFFIX"],
}
extension = spec.get("product_extension")
if extension:
extension = "." + extension
else:
extension = DEFAULT_EXTENSION.get(type, "")
extension = "." + extension if extension else DEFAULT_EXTENSION.get(type, "")
if "product_name" in spec:
# If we were given an explicit name, use that.
@ -1953,7 +1945,8 @@ class NinjaWriter:
)
else:
rspfile_content = gyp.msvs_emulation.EncodeRspFileList(
args, win_shell_flags.quote)
args, win_shell_flags.quote
)
command = (
"%s gyp-win-tool action-wrapper $arch " % sys.executable
+ rspfile
@ -1999,7 +1992,7 @@ def CalculateVariables(default_variables, params):
# Copy additional generator configuration data from Xcode, which is shared
# by the Mac Ninja generator.
import gyp.generator.xcode as xcode_generator
import gyp.generator.xcode as xcode_generator # noqa: PLC0415
generator_additional_non_configuration_keys = getattr(
xcode_generator, "generator_additional_non_configuration_keys", []
@ -2022,7 +2015,7 @@ def CalculateVariables(default_variables, params):
# Copy additional generator configuration data from VS, which is shared
# by the Windows Ninja generator.
import gyp.generator.msvs as msvs_generator
import gyp.generator.msvs as msvs_generator # noqa: PLC0415
generator_additional_non_configuration_keys = getattr(
msvs_generator, "generator_additional_non_configuration_keys", []
@ -2079,20 +2072,17 @@ def OpenOutput(path, mode="w"):
def CommandWithWrapper(cmd, wrappers, prog):
wrapper = wrappers.get(cmd, "")
if wrapper:
if wrapper := wrappers.get(cmd, ""):
return wrapper + " " + prog
return prog
def GetDefaultConcurrentLinks():
"""Returns a best-guess for a number of concurrent links."""
pool_size = int(os.environ.get("GYP_LINK_CONCURRENCY", 0))
if pool_size:
if pool_size := int(os.environ.get("GYP_LINK_CONCURRENCY") or 0):
return pool_size
if sys.platform in ("win32", "cygwin"):
import ctypes
class MEMORYSTATUSEX(ctypes.Structure):
_fields_ = [
@ -2113,8 +2103,8 @@ def GetDefaultConcurrentLinks():
# VS 2015 uses 20% more working set than VS 2013 and can consume all RAM
# on a 64 GiB machine.
mem_limit = max(1, stat.ullTotalPhys // (5 * (2 ** 30))) # total / 5GiB
hard_cap = max(1, int(os.environ.get("GYP_LINK_CONCURRENCY_MAX", 2 ** 32)))
mem_limit = max(1, stat.ullTotalPhys // (5 * (2**30))) # total / 5GiB
hard_cap = max(1, int(os.environ.get("GYP_LINK_CONCURRENCY_MAX") or 2**32))
return min(mem_limit, hard_cap)
elif sys.platform.startswith("linux"):
if os.path.exists("/proc/meminfo"):
@ -2125,14 +2115,14 @@ def GetDefaultConcurrentLinks():
if not match:
continue
# Allow 8Gb per link on Linux because Gold is quite memory hungry
return max(1, int(match.group(1)) // (8 * (2 ** 20)))
return max(1, int(match.group(1)) // (8 * (2**20)))
return 1
elif sys.platform == "darwin":
try:
avail_bytes = int(subprocess.check_output(["sysctl", "-n", "hw.memsize"]))
# A static library debug build of Chromium's unit_tests takes ~2.7GB, so
# 4GB per ld process allows for some more bloat.
return max(1, avail_bytes // (4 * (2 ** 30))) # total / 4GB
return max(1, avail_bytes // (4 * (2**30))) # total / 4GB
except subprocess.CalledProcessError:
return 1
else:
@ -2213,6 +2203,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
options = params["options"]
flavor = gyp.common.GetFlavor(params)
generator_flags = params.get("generator_flags", {})
generate_compile_commands = generator_flags.get("compile_commands", False)
# build_dir: relative path from source root to our output files.
# e.g. "out/Debug"
@ -2308,8 +2299,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
key_prefix = re.sub(r"\.HOST$", ".host", key_prefix)
wrappers[key_prefix] = os.path.join(build_to_root, value)
mac_toolchain_dir = generator_flags.get("mac_toolchain_dir", None)
if mac_toolchain_dir:
if mac_toolchain_dir := generator_flags.get("mac_toolchain_dir", None):
wrappers["LINK"] = "export DEVELOPER_DIR='%s' &&" % mac_toolchain_dir
if flavor == "win":
@ -2349,6 +2339,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
master_ninja.variable("rc", "rc.exe")
master_ninja.variable("ml_x86", "ml.exe")
master_ninja.variable("ml_x64", "ml64.exe")
master_ninja.variable("ml_arm64", "armasm64.exe")
master_ninja.variable("mt", "mt.exe")
else:
master_ninja.variable("ld", CommandWithWrapper("LINK", wrappers, ld))
@ -2420,8 +2411,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
"cc_s",
description="CC $out",
command=(
"$cc $defines $includes $cflags $cflags_c "
"$cflags_pch_c -c $in -o $out"
"$cc $defines $includes $cflags $cflags_c $cflags_pch_c -c $in -o $out"
),
)
master_ninja.rule(
@ -2532,11 +2522,10 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
"solink",
description="SOLINK $lib",
restat=True,
command=mtime_preserving_solink_base
% {"suffix": "@$link_file_list"}, # noqa: E501
command=mtime_preserving_solink_base % {"suffix": "@$link_file_list"},
rspfile="$link_file_list",
rspfile_content=(
"-Wl,--whole-archive $in $solibs -Wl," "--no-whole-archive $libs"
"-Wl,--whole-archive $in $solibs -Wl,--no-whole-archive $libs"
),
pool="link_pool",
)
@ -2596,9 +2585,9 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
"alink",
description="LIBTOOL-STATIC $out, POSTBUILDS",
command="rm -f $out && "
"./gyp-mac-tool filter-libtool libtool $libtool_flags "
"%s gyp-mac-tool filter-libtool libtool $libtool_flags "
"-static -o $out $in"
"$postbuilds",
"$postbuilds" % sys.executable,
)
master_ninja.rule(
"lipo",
@ -2685,7 +2674,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
master_ninja.rule(
"link",
description="LINK $out, POSTBUILDS",
command=("$ld $ldflags -o $out " "$in $solibs $libs$postbuilds"),
command=("$ld $ldflags -o $out $in $solibs $libs$postbuilds"),
pool="link_pool",
)
master_ninja.rule(
@ -2699,41 +2688,44 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
master_ninja.rule(
"copy_infoplist",
description="COPY INFOPLIST $in",
command="$env ./gyp-mac-tool copy-info-plist $in $out $binary $keys",
command="$env %s gyp-mac-tool copy-info-plist $in $out $binary $keys"
% sys.executable,
)
master_ninja.rule(
"merge_infoplist",
description="MERGE INFOPLISTS $in",
command="$env ./gyp-mac-tool merge-info-plist $out $in",
command="$env %s gyp-mac-tool merge-info-plist $out $in" % sys.executable,
)
master_ninja.rule(
"compile_xcassets",
description="COMPILE XCASSETS $in",
command="$env ./gyp-mac-tool compile-xcassets $keys $in",
command="$env %s gyp-mac-tool compile-xcassets $keys $in" % sys.executable,
)
master_ninja.rule(
"compile_ios_framework_headers",
description="COMPILE HEADER MAPS AND COPY FRAMEWORK HEADERS $in",
command="$env ./gyp-mac-tool compile-ios-framework-header-map $out "
"$framework $in && $env ./gyp-mac-tool "
"copy-ios-framework-headers $framework $copy_headers",
command="$env %(python)s gyp-mac-tool compile-ios-framework-header-map "
"$out $framework $in && $env %(python)s gyp-mac-tool "
"copy-ios-framework-headers $framework $copy_headers"
% {"python": sys.executable},
)
master_ninja.rule(
"mac_tool",
description="MACTOOL $mactool_cmd $in",
command="$env ./gyp-mac-tool $mactool_cmd $in $out $binary",
command="$env %s gyp-mac-tool $mactool_cmd $in $out $binary"
% sys.executable,
)
master_ninja.rule(
"package_framework",
description="PACKAGE FRAMEWORK $out, POSTBUILDS",
command="./gyp-mac-tool package-framework $out $version$postbuilds "
"&& touch $out",
command="%s gyp-mac-tool package-framework $out $version$postbuilds "
"&& touch $out" % sys.executable,
)
master_ninja.rule(
"package_ios_framework",
description="PACKAGE IOS FRAMEWORK $out, POSTBUILDS",
command="./gyp-mac-tool package-ios-framework $out $postbuilds "
"&& touch $out",
command="%s gyp-mac-tool package-ios-framework $out $postbuilds "
"&& touch $out" % sys.executable,
)
if flavor == "win":
master_ninja.rule(
@ -2811,7 +2803,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
build_file, name, toolset
)
qualified_target_for_hash = qualified_target_for_hash.encode("utf-8")
hash_for_rules = hashlib.md5(qualified_target_for_hash).hexdigest()
hash_for_rules = hashlib.sha256(qualified_target_for_hash).hexdigest()
base_path = os.path.dirname(build_file)
obj = "obj"
@ -2881,6 +2873,35 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
master_ninja_file.close()
if generate_compile_commands:
compile_db = GenerateCompileDBWithNinja(toplevel_build)
compile_db_file = OpenOutput(
os.path.join(toplevel_build, "compile_commands.json")
)
compile_db_file.write(json.dumps(compile_db, indent=2))
compile_db_file.close()
def GenerateCompileDBWithNinja(path, targets=["all"]):
"""Generates a compile database using ninja.
Args:
path: The build directory to generate a compile database for.
targets: Additional targets to pass to ninja.
Returns:
List of the contents of the compile database.
"""
ninja_path = shutil.which("ninja")
if ninja_path is None:
raise Exception("ninja not found in PATH")
json_compile_db = subprocess.check_output(
[ninja_path, "-C", path]
+ targets
+ ["-t", "compdb", "cc", "cxx", "objc", "objcxx"]
)
return json.loads(json_compile_db)
def PerformBuild(data, configurations, params):
options = params["options"]

View file

@ -4,32 +4,43 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Unit tests for the ninja.py file. """
"""Unit tests for the ninja.py file."""
import sys
import unittest
from pathlib import Path
import gyp.generator.ninja as ninja
from gyp.generator import ninja
from gyp.MSVSVersion import SelectVisualStudioVersion
def _has_visual_studio():
"""Check if Visual Studio can be detected by gyp's registry-based detection."""
if not sys.platform.startswith("win"):
return False
try:
SelectVisualStudioVersion("auto", allow_fallback=False)
return True
except ValueError:
return False
class TestPrefixesAndSuffixes(unittest.TestCase):
@unittest.skipUnless(
_has_visual_studio(),
"requires Windows with a Visual Studio installation detected via the registry",
)
def test_BinaryNamesWindows(self):
# These cannot run on non-Windows as they require a VS installation to
# correctly handle variable expansion.
if sys.platform.startswith("win"):
writer = ninja.NinjaWriter(
"foo", "wee", ".", ".", "build.ninja", ".", "build.ninja", "win"
)
spec = {"target_name": "wee"}
self.assertTrue(
writer.ComputeOutputFileName(spec, "executable").endswith(".exe")
)
self.assertTrue(
writer.ComputeOutputFileName(spec, "shared_library").endswith(".dll")
)
self.assertTrue(
writer.ComputeOutputFileName(spec, "static_library").endswith(".lib")
)
writer = ninja.NinjaWriter(
"foo", "wee", ".", ".", "build.ninja", ".", "build.ninja", "win"
)
spec = {"target_name": "wee"}
for key, ext in {
"executable": ".exe",
"shared_library": ".dll",
"static_library": ".lib",
}.items():
self.assertTrue(writer.ComputeOutputFileName(spec, key).endswith(ext))
def test_BinaryNamesLinux(self):
writer = ninja.NinjaWriter(
@ -50,6 +61,17 @@ class TestPrefixesAndSuffixes(unittest.TestCase):
writer.ComputeOutputFileName(spec, "static_library").endswith(".a")
)
def test_GenerateCompileDBWithNinja(self):
build_dir = (
Path(__file__).resolve().parent.parent.parent.parent / "data" / "ninja"
)
compile_db = ninja.GenerateCompileDBWithNinja(build_dir)
assert len(compile_db) == 1
assert compile_db[0]["directory"] == str(build_dir)
assert compile_db[0]["command"] == "cc my.in my.out"
assert compile_db[0]["file"] == "my.in"
assert compile_db[0]["output"] == "my.out"
if __name__ == "__main__":
unittest.main()

View file

@ -3,19 +3,19 @@
# found in the LICENSE file.
import filecmp
import gyp.common
import gyp.xcodeproj_file
import gyp.xcode_ninja
import errno
import filecmp
import os
import sys
import posixpath
import re
import shutil
import subprocess
import sys
import tempfile
import gyp.common
import gyp.xcode_ninja
import gyp.xcodeproj_file
# Project files generated by this module will use _intermediate_var as a
# custom Xcode setting whose value is a DerivedSources-like directory that's
@ -439,7 +439,7 @@ sys.exit(subprocess.call(sys.argv[1:]))" """
# it opens the project file, which will result in unnecessary diffs.
# TODO(mark): This is evil because it relies on internal knowledge of
# PBXProject._other_pbxprojects.
for other_pbxproject in self.project._other_pbxprojects.keys():
for other_pbxproject in self.project._other_pbxprojects:
self.project.AddOrGetProjectReference(other_pbxproject)
self.project.SortRemoteProductReferences()
@ -531,7 +531,7 @@ def AddSourceToTarget(source, type, pbxp, xct):
library_extensions = ["a", "dylib", "framework", "o"]
basename = posixpath.basename(source)
(root, ext) = posixpath.splitext(basename)
(_root, ext) = posixpath.splitext(basename)
if ext:
ext = ext[1:].lower()
@ -564,12 +564,12 @@ _xcode_variable_re = re.compile(r"(\$\((.*?)\))")
def ExpandXcodeVariables(string, expansions):
"""Expands Xcode-style $(VARIABLES) in string per the expansions dict.
In some rare cases, it is appropriate to expand Xcode variables when a
project file is generated. For any substring $(VAR) in string, if VAR is a
key in the expansions dict, $(VAR) will be replaced with expansions[VAR].
Any $(VAR) substring in string for which VAR is not a key in the expansions
dict will remain in the returned string.
"""
In some rare cases, it is appropriate to expand Xcode variables when a
project file is generated. For any substring $(VAR) in string, if VAR is a
key in the expansions dict, $(VAR) will be replaced with expansions[VAR].
Any $(VAR) substring in string for which VAR is not a key in the expansions
dict will remain in the returned string.
"""
matches = _xcode_variable_re.findall(string)
if matches is None:
@ -592,9 +592,9 @@ _xcode_define_re = re.compile(r"([\\\"\' ])")
def EscapeXcodeDefine(s):
"""We must escape the defines that we give to XCode so that it knows not to
split on spaces and to respect backslash and quote literals. However, we
must not quote the define, or Xcode will incorrectly interpret variables
especially $(inherited)."""
split on spaces and to respect backslash and quote literals. However, we
must not quote the define, or Xcode will incorrectly interpret variables
especially $(inherited)."""
return re.sub(_xcode_define_re, r"\\\1", s)
@ -679,9 +679,9 @@ def GenerateOutput(target_list, target_dicts, data, params):
project_attributes["BuildIndependentTargetsInParallel"] = "YES"
if upgrade_check_project_version:
project_attributes["LastUpgradeCheck"] = upgrade_check_project_version
project_attributes[
"LastTestingUpgradeCheck"
] = upgrade_check_project_version
project_attributes["LastTestingUpgradeCheck"] = (
upgrade_check_project_version
)
project_attributes["LastSwiftUpdateCheck"] = upgrade_check_project_version
pbxp.SetProperty("attributes", project_attributes)
@ -696,7 +696,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
xcode_targets = {}
xcode_target_to_target_dict = {}
for qualified_target in target_list:
[build_file, target_name, toolset] = gyp.common.ParseQualifiedTarget(
[build_file, target_name, _toolset] = gyp.common.ParseQualifiedTarget(
qualified_target
)
@ -734,8 +734,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
"loadable_module+xcuitest": "com.apple.product-type.bundle.ui-testing",
"shared_library+bundle": "com.apple.product-type.framework",
"executable+extension+bundle": "com.apple.product-type.app-extension",
"executable+watch+extension+bundle":
"com.apple.product-type.watchkit-extension",
"executable+watch+extension+bundle": "com.apple.product-type.watchkit-extension", # noqa: E501
"executable+watch+bundle": "com.apple.product-type.application.watchapp",
"mac_kernel_extension+bundle": "com.apple.product-type.kernel-extension",
}
@ -780,8 +779,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
type_bundle_key += "+watch+extension+bundle"
elif is_watch_app:
assert is_bundle, (
"ios_watch_app flag requires mac_bundle "
"(target %s)" % target_name
"ios_watch_app flag requires mac_bundle (target %s)" % target_name
)
type_bundle_key += "+watch+bundle"
elif is_bundle:
@ -793,7 +791,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
except KeyError as e:
gyp.common.ExceptionAppend(
e,
"-- unknown product type while " "writing target %s" % target_name,
"-- unknown product type while writing target %s" % target_name,
)
raise
else:
@ -959,7 +957,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
# would-be additional inputs are newer than the output. Modifying
# the source tree - even just modification times - feels dirty.
# 6564240 Xcode "custom script" build rules always dump all environment
# variables. This is a low-prioroty problem and is not a
# variables. This is a low-priority problem and is not a
# show-stopper.
rules_by_ext = {}
for rule in spec_rules:
@ -1103,7 +1101,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
eol = " \\"
makefile.write(f" {concrete_output}{eol}\n")
for (rule_source, concrete_outputs, message, action) in zip(
for rule_source, concrete_outputs, message, action in zip(
rule["rule_sources"],
concrete_outputs_by_rule_source,
messages,
@ -1118,10 +1116,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
for concrete_output_index, concrete_output in enumerate(
concrete_outputs
):
if concrete_output_index == 0:
bol = ""
else:
bol = " "
bol = "" if concrete_output_index == 0 else " "
makefile.write(f"{bol}{concrete_output} \\\n")
concrete_output_dir = posixpath.dirname(concrete_output)
@ -1220,7 +1215,7 @@ exit 1
# Add "sources".
for source in spec.get("sources", []):
(source_root, source_extension) = posixpath.splitext(source)
(_source_root, source_extension) = posixpath.splitext(source)
if source_extension[1:] not in rules_by_ext:
# AddSourceToTarget will add the file to a root group if it's not
# already there.
@ -1232,7 +1227,7 @@ exit 1
# it's a bundle of any type.
if is_bundle:
for resource in tgt_mac_bundle_resources:
(resource_root, resource_extension) = posixpath.splitext(resource)
(_resource_root, resource_extension) = posixpath.splitext(resource)
if resource_extension[1:] not in rules_by_ext:
AddResourceToTarget(resource, pbxp, xct)
else:

View file

@ -4,11 +4,12 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Unit tests for the xcode.py file. """
"""Unit tests for the xcode.py file."""
import gyp.generator.xcode as xcode
import unittest
import sys
import unittest
from gyp.generator import xcode
class TestEscapeXcodeDefine(unittest.TestCase):

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,10 @@
"""Unit tests for the input.py file."""
import gyp.input
import unittest
import gyp.input
class TestFindCycles(unittest.TestCase):
def setUp(self):

View file

@ -8,7 +8,6 @@
These functions are executed via gyp-mac-tool when using the Makefile generator.
"""
import fcntl
import fnmatch
import glob
@ -25,14 +24,13 @@ import tempfile
def main(args):
executor = MacTool()
exit_code = executor.Dispatch(args)
if exit_code is not None:
if (exit_code := executor.Dispatch(args)) is not None:
sys.exit(exit_code)
class MacTool:
"""This class performs all the Mac tooling steps. The methods can either be
executed directly, or dispatched from an argument list."""
executed directly, or dispatched from an argument list."""
def Dispatch(self, args):
"""Dispatches a string command to a method."""
@ -48,7 +46,7 @@ class MacTool:
def ExecCopyBundleResource(self, source, dest, convert_to_binary):
"""Copies a resource file to the bundle/Resources directory, performing any
necessary compilation on each resource."""
necessary compilation on each resource."""
convert_to_binary = convert_to_binary == "True"
extension = os.path.splitext(source)[1].lower()
if os.path.isdir(source):
@ -59,9 +57,7 @@ class MacTool:
if os.path.exists(dest):
shutil.rmtree(dest)
shutil.copytree(source, dest)
elif extension == ".xib":
return self._CopyXIBFile(source, dest)
elif extension == ".storyboard":
elif extension in {".xib", ".storyboard"}:
return self._CopyXIBFile(source, dest)
elif extension == ".strings" and not convert_to_binary:
self._CopyStringsFile(source, dest)
@ -70,7 +66,7 @@ class MacTool:
os.unlink(dest)
shutil.copy(source, dest)
if convert_to_binary and extension in (".plist", ".strings"):
if convert_to_binary and extension in {".plist", ".strings"}:
self._ConvertToBinary(dest)
def _CopyXIBFile(self, source, dest):
@ -144,7 +140,7 @@ class MacTool:
# CFPropertyListCreateFromXMLData(): Old-style plist parser: missing
# semicolon in dictionary.
# on invalid files. Do the same kind of validation.
import CoreFoundation
import CoreFoundation # noqa: PLC0415
with open(source, "rb") as in_file:
s = in_file.read()
@ -158,17 +154,15 @@ class MacTool:
def _DetectInputEncoding(self, file_name):
"""Reads the first few bytes from file_name and tries to guess the text
encoding. Returns None as a guess if it can't detect it."""
encoding. Returns None as a guess if it can't detect it."""
with open(file_name, "rb") as fp:
try:
header = fp.read(3)
except Exception:
return None
if header.startswith(b"\xFE\xFF"):
if header.startswith((b"\xfe\xff", b"\xff\xfe")):
return "UTF-16"
elif header.startswith(b"\xFF\xFE"):
return "UTF-16"
elif header.startswith(b"\xEF\xBB\xBF"):
elif header.startswith(b"\xef\xbb\xbf"):
return "UTF-8"
else:
return None
@ -259,9 +253,9 @@ class MacTool:
def ExecFilterLibtool(self, *cmd_list):
"""Calls libtool and filters out '/path/to/libtool: file: foo.o has no
symbols'."""
symbols'."""
libtool_re = re.compile(
r"^.*libtool: (?:for architecture: \S* )?" r"file: .* has no symbols$"
r"^.*libtool: (?:for architecture: \S* )?file: .* has no symbols$"
)
libtool_re5 = re.compile(
r"^.*libtool: warning for library: "
@ -308,7 +302,7 @@ class MacTool:
def ExecPackageFramework(self, framework, version):
"""Takes a path to Something.framework and the Current version of that and
sets up all the symlinks."""
sets up all the symlinks."""
# Find the name of the binary based on the part before the ".framework".
binary = os.path.basename(framework).split(".")[0]
@ -337,7 +331,7 @@ class MacTool:
def _Relink(self, dest, link):
"""Creates a symlink to |dest| named |link|. If |link| already exists,
it is overwritten."""
it is overwritten."""
if os.path.lexists(link):
os.remove(link)
os.symlink(dest, link)
@ -362,14 +356,14 @@ class MacTool:
def ExecCompileXcassets(self, keys, *inputs):
"""Compiles multiple .xcassets files into a single .car file.
This invokes 'actool' to compile all the inputs .xcassets files. The
|keys| arguments is a json-encoded dictionary of extra arguments to
pass to 'actool' when the asset catalogs contains an application icon
or a launch image.
This invokes 'actool' to compile all the inputs .xcassets files. The
|keys| arguments is a json-encoded dictionary of extra arguments to
pass to 'actool' when the asset catalogs contains an application icon
or a launch image.
Note that 'actool' does not create the Assets.car file if the asset
catalogs does not contains imageset.
"""
Note that 'actool' does not create the Assets.car file if the asset
catalogs does not contains imageset.
"""
command_line = [
"xcrun",
"actool",
@ -442,13 +436,13 @@ class MacTool:
def ExecCodeSignBundle(self, key, entitlements, provisioning, path, preserve):
"""Code sign a bundle.
This function tries to code sign an iOS bundle, following the same
algorithm as Xcode:
1. pick the provisioning profile that best match the bundle identifier,
and copy it into the bundle as embedded.mobileprovision,
2. copy Entitlements.plist from user or SDK next to the bundle,
3. code sign the bundle.
"""
This function tries to code sign an iOS bundle, following the same
algorithm as Xcode:
1. pick the provisioning profile that best match the bundle identifier,
and copy it into the bundle as embedded.mobileprovision,
2. copy Entitlements.plist from user or SDK next to the bundle,
3. code sign the bundle.
"""
substitutions, overrides = self._InstallProvisioningProfile(
provisioning, self._GetCFBundleIdentifier()
)
@ -467,16 +461,16 @@ class MacTool:
def _InstallProvisioningProfile(self, profile, bundle_identifier):
"""Installs embedded.mobileprovision into the bundle.
Args:
profile: string, optional, short name of the .mobileprovision file
to use, if empty or the file is missing, the best file installed
will be used
bundle_identifier: string, value of CFBundleIdentifier from Info.plist
Args:
profile: string, optional, short name of the .mobileprovision file
to use, if empty or the file is missing, the best file installed
will be used
bundle_identifier: string, value of CFBundleIdentifier from Info.plist
Returns:
A tuple containing two dictionary: variables substitutions and values
to overrides when generating the entitlements file.
"""
Returns:
A tuple containing two dictionary: variables substitutions and values
to overrides when generating the entitlements file.
"""
source_path, provisioning_data, team_id = self._FindProvisioningProfile(
profile, bundle_identifier
)
@ -492,24 +486,24 @@ class MacTool:
def _FindProvisioningProfile(self, profile, bundle_identifier):
"""Finds the .mobileprovision file to use for signing the bundle.
Checks all the installed provisioning profiles (or if the user specified
the PROVISIONING_PROFILE variable, only consult it) and select the most
specific that correspond to the bundle identifier.
Checks all the installed provisioning profiles (or if the user specified
the PROVISIONING_PROFILE variable, only consult it) and select the most
specific that correspond to the bundle identifier.
Args:
profile: string, optional, short name of the .mobileprovision file
to use, if empty or the file is missing, the best file installed
will be used
bundle_identifier: string, value of CFBundleIdentifier from Info.plist
Args:
profile: string, optional, short name of the .mobileprovision file
to use, if empty or the file is missing, the best file installed
will be used
bundle_identifier: string, value of CFBundleIdentifier from Info.plist
Returns:
A tuple of the path to the selected provisioning profile, the data of
the embedded plist in the provisioning profile and the team identifier
to use for code signing.
Returns:
A tuple of the path to the selected provisioning profile, the data of
the embedded plist in the provisioning profile and the team identifier
to use for code signing.
Raises:
SystemExit: if no .mobileprovision can be used to sign the bundle.
"""
Raises:
SystemExit: if no .mobileprovision can be used to sign the bundle.
"""
profiles_dir = os.path.join(
os.environ["HOME"], "Library", "MobileDevice", "Provisioning Profiles"
)
@ -551,18 +545,18 @@ class MacTool:
# If the user has multiple provisioning profiles installed that can be
# used for ${bundle_identifier}, pick the most specific one (ie. the
# provisioning profile whose pattern is the longest).
selected_key = max(valid_provisioning_profiles, key=lambda v: len(v))
selected_key = max(valid_provisioning_profiles, key=len)
return valid_provisioning_profiles[selected_key]
def _LoadProvisioningProfile(self, profile_path):
"""Extracts the plist embedded in a provisioning profile.
Args:
profile_path: string, path to the .mobileprovision file
Args:
profile_path: string, path to the .mobileprovision file
Returns:
Content of the plist embedded in the provisioning profile as a dictionary.
"""
Returns:
Content of the plist embedded in the provisioning profile as a dictionary.
"""
with tempfile.NamedTemporaryFile() as temp:
subprocess.check_call(
["security", "cms", "-D", "-i", profile_path, "-o", temp.name]
@ -585,16 +579,16 @@ class MacTool:
def _LoadPlistMaybeBinary(self, plist_path):
"""Loads into a memory a plist possibly encoded in binary format.
This is a wrapper around plistlib.readPlist that tries to convert the
plist to the XML format if it can't be parsed (assuming that it is in
the binary format).
This is a wrapper around plistlib.readPlist that tries to convert the
plist to the XML format if it can't be parsed (assuming that it is in
the binary format).
Args:
plist_path: string, path to a plist file, in XML or binary format
Args:
plist_path: string, path to a plist file, in XML or binary format
Returns:
Content of the plist as a dictionary.
"""
Returns:
Content of the plist as a dictionary.
"""
try:
# First, try to read the file using plistlib that only supports XML,
# and if an exception is raised, convert a temporary copy to XML and
@ -610,13 +604,13 @@ class MacTool:
def _GetSubstitutions(self, bundle_identifier, app_identifier_prefix):
"""Constructs a dictionary of variable substitutions for Entitlements.plist.
Args:
bundle_identifier: string, value of CFBundleIdentifier from Info.plist
app_identifier_prefix: string, value for AppIdentifierPrefix
Args:
bundle_identifier: string, value of CFBundleIdentifier from Info.plist
app_identifier_prefix: string, value for AppIdentifierPrefix
Returns:
Dictionary of substitutions to apply when generating Entitlements.plist.
"""
Returns:
Dictionary of substitutions to apply when generating Entitlements.plist.
"""
return {
"CFBundleIdentifier": bundle_identifier,
"AppIdentifierPrefix": app_identifier_prefix,
@ -625,9 +619,9 @@ class MacTool:
def _GetCFBundleIdentifier(self):
"""Extracts CFBundleIdentifier value from Info.plist in the bundle.
Returns:
Value of CFBundleIdentifier in the Info.plist located in the bundle.
"""
Returns:
Value of CFBundleIdentifier in the Info.plist located in the bundle.
"""
info_plist_path = os.path.join(
os.environ["TARGET_BUILD_DIR"], os.environ["INFOPLIST_PATH"]
)
@ -637,19 +631,19 @@ class MacTool:
def _InstallEntitlements(self, entitlements, substitutions, overrides):
"""Generates and install the ${BundleName}.xcent entitlements file.
Expands variables "$(variable)" pattern in the source entitlements file,
add extra entitlements defined in the .mobileprovision file and the copy
the generated plist to "${BundlePath}.xcent".
Expands variables "$(variable)" pattern in the source entitlements file,
add extra entitlements defined in the .mobileprovision file and the copy
the generated plist to "${BundlePath}.xcent".
Args:
entitlements: string, optional, path to the Entitlements.plist template
to use, defaults to "${SDKROOT}/Entitlements.plist"
substitutions: dictionary, variable substitutions
overrides: dictionary, values to add to the entitlements
Args:
entitlements: string, optional, path to the Entitlements.plist template
to use, defaults to "${SDKROOT}/Entitlements.plist"
substitutions: dictionary, variable substitutions
overrides: dictionary, values to add to the entitlements
Returns:
Path to the generated entitlements file.
"""
Returns:
Path to the generated entitlements file.
"""
source_path = entitlements
target_path = os.path.join(
os.environ["BUILT_PRODUCTS_DIR"], os.environ["PRODUCT_NAME"] + ".xcent"
@ -669,15 +663,15 @@ class MacTool:
def _ExpandVariables(self, data, substitutions):
"""Expands variables "$(variable)" in data.
Args:
data: object, can be either string, list or dictionary
substitutions: dictionary, variable substitutions to perform
Args:
data: object, can be either string, list or dictionary
substitutions: dictionary, variable substitutions to perform
Returns:
Copy of data where each references to "$(variable)" has been replaced
by the corresponding value found in substitutions, or left intact if
the key was not found.
"""
Returns:
Copy of data where each references to "$(variable)" has been replaced
by the corresponding value found in substitutions, or left intact if
the key was not found.
"""
if isinstance(data, str):
for key, value in substitutions.items():
data = data.replace("$(%s)" % key, value)
@ -696,15 +690,15 @@ def NextGreaterPowerOf2(x):
def WriteHmap(output_name, filelist):
"""Generates a header map based on |filelist|.
Per Mark Mentovai:
A header map is structured essentially as a hash table, keyed by names used
in #includes, and providing pathnames to the actual files.
Per Mark Mentovai:
A header map is structured essentially as a hash table, keyed by names used
in #includes, and providing pathnames to the actual files.
The implementation below and the comment above comes from inspecting:
http://www.opensource.apple.com/source/distcc/distcc-2503/distcc_dist/include_server/headermap.py?txt
while also looking at the implementation in clang in:
https://llvm.org/svn/llvm-project/cfe/trunk/lib/Lex/HeaderMap.cpp
"""
The implementation below and the comment above comes from inspecting:
http://www.opensource.apple.com/source/distcc/distcc-2503/distcc_dist/include_server/headermap.py?txt
while also looking at the implementation in clang in:
https://llvm.org/svn/llvm-project/cfe/trunk/lib/Lex/HeaderMap.cpp
"""
magic = 1751998832
version = 1
_reserved = 0

View file

@ -7,15 +7,15 @@ This module helps emulate Visual Studio 2008 behavior on top of other
build systems, primarily ninja.
"""
import collections
import os
import re
import subprocess
import sys
from collections import namedtuple
from gyp.common import OrderedSet
import gyp.MSVSUtil
import gyp.MSVSVersion
from gyp.common import OrderedSet
windows_quoter_regex = re.compile(r'(\\*)"')
@ -74,8 +74,7 @@ def EncodeRspFileList(args, quote_cmd):
program = call + " " + os.path.normpath(program)
else:
program = os.path.normpath(args[0])
return (program + " "
+ " ".join(QuoteForRspFile(arg, quote_cmd) for arg in args[1:]))
return program + " " + " ".join(QuoteForRspFile(arg, quote_cmd) for arg in args[1:])
def _GenericRetrieve(root, default, path):
@ -93,7 +92,7 @@ def _AddPrefix(element, prefix):
if element is None:
return element
# Note, not Iterable because we don't want to handle strings like that.
if isinstance(element, list) or isinstance(element, tuple):
if isinstance(element, (list, tuple)):
return [prefix + e for e in element]
else:
return prefix + element
@ -105,7 +104,7 @@ def _DoRemapping(element, map):
if map is not None and element is not None:
if not callable(map):
map = map.get # Assume it's a dict, otherwise a callable to do the remap.
if isinstance(element, list) or isinstance(element, tuple):
if isinstance(element, (list, tuple)):
element = filter(None, [map(elem) for elem in element])
else:
element = map(element)
@ -117,7 +116,7 @@ def _AppendOrReturn(append, element):
then add |element| to it, adding each item in |element| if it's a list or
tuple."""
if append is not None and element is not None:
if isinstance(element, list) or isinstance(element, tuple):
if isinstance(element, (list, tuple)):
append.extend(element)
else:
append.append(element)
@ -183,7 +182,7 @@ def ExtractSharedMSVSSystemIncludes(configs, generator_flags):
expanded_system_includes = OrderedSet(
[ExpandMacros(include, env) for include in all_system_includes]
)
if any(["$" in include for include in expanded_system_includes]):
if any("$" in include for include in expanded_system_includes):
# Some path relies on target-specific variables, bail.
return None
@ -247,18 +246,13 @@ class MsvsSettings:
the target type.
"""
ext = self.spec.get("product_extension", None)
if ext:
return ext
return gyp.MSVSUtil.TARGET_TYPE_EXT.get(self.spec["type"], "")
return ext or gyp.MSVSUtil.TARGET_TYPE_EXT.get(self.spec["type"], "")
def GetVSMacroEnv(self, base_to_build=None, config=None):
"""Get a dict of variables mapping internal VS macro names to their gyp
equivalents."""
target_arch = self.GetArch(config)
if target_arch == "x86":
target_platform = "Win32"
else:
target_platform = target_arch
target_platform = "Win32" if target_arch == "x86" else target_arch
target_name = self.spec.get("product_prefix", "") + self.spec.get(
"product_name", self.spec["target_name"]
)
@ -628,8 +622,7 @@ class MsvsSettings:
def _GetDefFileAsLdflags(self, ldflags, gyp_to_build_path):
""".def files get implicitly converted to a ModuleDefinitionFile for the
linker in the VS generator. Emulate that behaviour here."""
def_file = self.GetDefFile(gyp_to_build_path)
if def_file:
if def_file := self.GetDefFile(gyp_to_build_path):
ldflags.append('/DEF:"%s"' % def_file)
def GetPGDName(self, config, expand_special):
@ -677,14 +670,11 @@ class MsvsSettings:
)
ld("DelayLoadDLLs", prefix="/DELAYLOAD:")
ld("TreatLinkerWarningAsErrors", prefix="/WX", map={"true": "", "false": ":NO"})
out = self.GetOutputName(config, expand_special)
if out:
if out := self.GetOutputName(config, expand_special):
ldflags.append("/OUT:" + out)
pdb = self.GetPDBName(config, expand_special, output_name + ".pdb")
if pdb:
if pdb := self.GetPDBName(config, expand_special, output_name + ".pdb"):
ldflags.append("/PDB:" + pdb)
pgd = self.GetPGDName(config, expand_special)
if pgd:
if pgd := self.GetPGDName(config, expand_special):
ldflags.append("/PGD:" + pgd)
map_file = self.GetMapFileName(config, expand_special)
ld("GenerateMapFile", map={"true": "/MAP:" + map_file if map_file else "/MAP"})
@ -738,10 +728,7 @@ class MsvsSettings:
# TODO(scottmg): This should sort of be somewhere else (not really a flag).
ld("AdditionalDependencies", prefix="")
if self.GetArch(config) == "x86":
safeseh_default = "true"
else:
safeseh_default = None
safeseh_default = "true" if self.GetArch(config) == "x86" else None
ld(
"ImageHasSafeExceptionHandlers",
map={"false": ":NO", "true": ""},
@ -836,17 +823,15 @@ class MsvsSettings:
("VCLinkerTool", "UACUIAccess"), config, default="false"
)
inner = """
level = execution_level_map[execution_level]
inner = f"""
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level='{}' uiAccess='{}' />
<requestedExecutionLevel level='{level}' uiAccess='{ui_access}' />
</requestedPrivileges>
</security>
</trustInfo>""".format(
execution_level_map[execution_level],
ui_access,
)
</trustInfo>"""
else:
inner = ""
@ -941,34 +926,34 @@ class MsvsSettings:
)
return cmd
RuleShellFlags = collections.namedtuple("RuleShellFlags", ["cygwin", "quote"])
RuleShellFlags = namedtuple("RuleShellFlags", ["cygwin", "quote"]) # noqa: PYI024
def GetRuleShellFlags(self, rule):
"""Return RuleShellFlags about how the given rule should be run. This
includes whether it should run under cygwin (msvs_cygwin_shell), and
whether the commands should be quoted (msvs_quote_cmd)."""
# If the variable is unset, or set to 1 we use cygwin
cygwin = int(rule.get("msvs_cygwin_shell",
self.spec.get("msvs_cygwin_shell", 1))) != 0
cygwin = (
int(rule.get("msvs_cygwin_shell", self.spec.get("msvs_cygwin_shell", 1)))
!= 0
)
# Default to quoting. There's only a few special instances where the
# target command uses non-standard command line parsing and handle quotes
# and quote escaping differently.
quote_cmd = int(rule.get("msvs_quote_cmd", 1))
assert quote_cmd != 0 or cygwin != 1, \
"msvs_quote_cmd=0 only applicable for msvs_cygwin_shell=0"
assert quote_cmd != 0 or cygwin != 1, (
"msvs_quote_cmd=0 only applicable for msvs_cygwin_shell=0"
)
return MsvsSettings.RuleShellFlags(cygwin, quote_cmd)
def _HasExplicitRuleForExtension(self, spec, extension):
"""Determine if there's an explicit rule for a particular extension."""
for rule in spec.get("rules", []):
if rule["extension"] == extension:
return True
return False
return any(rule["extension"] == extension for rule in spec.get("rules", []))
def _HasExplicitIdlActions(self, spec):
"""Determine if an action should not run midl for .idl files."""
return any(
[action.get("explicit_idl_action", 0) for action in spec.get("actions", [])]
action.get("explicit_idl_action", 0) for action in spec.get("actions", [])
)
def HasExplicitIdlRulesOrActions(self, spec):
@ -1146,8 +1131,7 @@ def _ExtractImportantEnvironment(output_of_set):
for required in ("SYSTEMROOT", "TEMP", "TMP"):
if required not in env:
raise Exception(
'Environment variable "%s" '
"required to be set to valid path" % required
'Environment variable "%s" required to be set to valid path' % required
)
return env
@ -1190,7 +1174,7 @@ def GenerateEnvironmentFiles(
meet your requirement (e.g. for custom toolchains), you can pass
"-G ninja_use_custom_environment_files" to the gyp to suppress file
generation and use custom environment files prepared by yourself."""
archs = ("x86", "x64")
archs = ("x86", "x64", "arm64")
if generator_flags.get("ninja_use_custom_environment_files", 0):
cl_paths = {}
for arch in archs:

View file

@ -17,15 +17,15 @@ __all__ = ["Error", "deepcopy"]
def deepcopy(x):
"""Deep copy operation on gyp objects such as strings, ints, dicts
and lists. More than twice as fast as copy.deepcopy but much less
generic."""
and lists. More than twice as fast as copy.deepcopy but much less
generic."""
try:
return _deepcopy_dispatch[type(x)](x)
except KeyError:
raise Error(
"Unsupported type %s for deepcopy. Use copy.deepcopy "
+ "or expand simple_copy support." % type(x)
f"Unsupported type {type(x)} for deepcopy. Use copy.deepcopy "
+ "or expand simple_copy support."
)

View file

@ -9,13 +9,12 @@
These functions are executed via gyp-win-tool when using the ninja generator.
"""
import os
import re
import shutil
import subprocess
import stat
import string
import subprocess
import sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@ -27,18 +26,17 @@ _LINK_EXE_OUT_ARG = re.compile("/OUT:(?P<out>.+)$", re.IGNORECASE)
def main(args):
executor = WinTool()
exit_code = executor.Dispatch(args)
if exit_code is not None:
if (exit_code := executor.Dispatch(args)) is not None:
sys.exit(exit_code)
class WinTool:
"""This class performs all the Windows tooling steps. The methods can either
be executed directly, or dispatched from an argument list."""
be executed directly, or dispatched from an argument list."""
def _UseSeparateMspdbsrv(self, env, args):
"""Allows to use a unique instance of mspdbsrv.exe per linker instead of a
shared one."""
shared one."""
if len(args) < 1:
raise Exception("Not enough arguments")
@ -115,9 +113,9 @@ class WinTool:
def ExecLinkWrapper(self, arch, use_separate_mspdbsrv, *args):
"""Filter diagnostic output from link that looks like:
' Creating library ui.dll.lib and object ui.dll.exp'
This happens when there are exports from the dll or exe.
"""
' Creating library ui.dll.lib and object ui.dll.exp'
This happens when there are exports from the dll or exe.
"""
env = self._GetEnv(arch)
if use_separate_mspdbsrv == "True":
self._UseSeparateMspdbsrv(env, args)
@ -159,10 +157,10 @@ class WinTool:
mt,
rc,
intermediate_manifest,
*manifests
*manifests,
):
"""A wrapper for handling creating a manifest resource and then executing
a link command."""
a link command."""
# The 'normal' way to do manifests is to have link generate a manifest
# based on gathering dependencies from the object files, then merge that
# manifest with other manifests supplied as sources, convert the merged
@ -219,11 +217,10 @@ class WinTool:
our_manifest = "%(out)s.manifest" % variables
# Load and normalize the manifests. mt.exe sometimes removes whitespace,
# and sometimes doesn't unfortunately.
with open(our_manifest) as our_f:
with open(assert_manifest) as assert_f:
translator = str.maketrans('', '', string.whitespace)
our_data = our_f.read().translate(translator)
assert_data = assert_f.read().translate(translator)
with open(our_manifest) as our_f, open(assert_manifest) as assert_f:
translator = str.maketrans("", "", string.whitespace)
our_data = our_f.read().translate(translator)
assert_data = assert_f.read().translate(translator)
if our_data != assert_data:
os.unlink(out)
@ -247,8 +244,8 @@ class WinTool:
def ExecManifestWrapper(self, arch, *args):
"""Run manifest tool with environment set. Strip out undesirable warning
(some XML blocks are recognized by the OS loader, but not the manifest
tool)."""
(some XML blocks are recognized by the OS loader, but not the manifest
tool)."""
env = self._GetEnv(arch)
popen = subprocess.Popen(
args, shell=True, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
@ -261,8 +258,8 @@ class WinTool:
def ExecManifestToRc(self, arch, *args):
"""Creates a resource file pointing a SxS assembly manifest.
|args| is tuple containing path to resource file, path to manifest file
and resource name which can be "1" (for executables) or "2" (for DLLs)."""
|args| is tuple containing path to resource file, path to manifest file
and resource name which can be "1" (for executables) or "2" (for DLLs)."""
manifest_path, resource_path, resource_name = args
with open(resource_path, "w") as output:
output.write(
@ -272,8 +269,8 @@ class WinTool:
def ExecMidlWrapper(self, arch, outdir, tlb, h, dlldata, iid, proxy, idl, *flags):
"""Filter noisy filenames output from MIDL compile step that isn't
quietable via command line flags.
"""
quietable via command line flags.
"""
args = (
["midl", "/nologo"]
+ list(flags)
@ -329,7 +326,7 @@ class WinTool:
def ExecRcWrapper(self, arch, *args):
"""Filter logo banner from invocations of rc.exe. Older versions of RC
don't support the /nologo flag."""
don't support the /nologo flag."""
env = self._GetEnv(arch)
popen = subprocess.Popen(
args, shell=True, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
@ -346,7 +343,7 @@ class WinTool:
def ExecActionWrapper(self, arch, rspfile, *dir):
"""Runs an action command line from a response file using the environment
for |arch|. If |dir| is supplied, use that as the working directory."""
for |arch|. If |dir| is supplied, use that as the working directory."""
env = self._GetEnv(arch)
# TODO(scottmg): This is a temporary hack to get some specific variables
# through to actions that are set after gyp-time. http://crbug.com/333738.
@ -359,7 +356,7 @@ class WinTool:
def ExecClCompile(self, project_dir, selected_files):
"""Executed by msvs-ninja projects when the 'ClCompile' target is used to
build selected C/C++ files."""
build selected C/C++ files."""
project_dir = os.path.relpath(project_dir, BASE_DIR)
selected_files = selected_files.split(";")
ninja_targets = [

View file

@ -7,15 +7,15 @@ This module contains classes that help to emulate xcodebuild behavior on top of
other build systems, such as make and ninja.
"""
import copy
import gyp.common
import os
import os.path
import re
import shlex
import subprocess
import sys
import gyp.common
from gyp.common import GypError
# Populated lazily by XcodeVersion, for efficiency, and to fix an issue when
@ -30,7 +30,7 @@ XCODE_ARCHS_DEFAULT_CACHE = None
def XcodeArchsVariableMapping(archs, archs_including_64_bit=None):
"""Constructs a dictionary with expansion for $(ARCHS_STANDARD) variable,
and optionally for $(ARCHS_STANDARD_INCLUDING_64_BIT)."""
and optionally for $(ARCHS_STANDARD_INCLUDING_64_BIT)."""
mapping = {"$(ARCHS_STANDARD)": archs}
if archs_including_64_bit:
mapping["$(ARCHS_STANDARD_INCLUDING_64_BIT)"] = archs_including_64_bit
@ -39,10 +39,10 @@ def XcodeArchsVariableMapping(archs, archs_including_64_bit=None):
class XcodeArchsDefault:
"""A class to resolve ARCHS variable from xcode_settings, resolving Xcode
macros and implementing filtering by VALID_ARCHS. The expansion of macros
depends on the SDKROOT used ("macosx", "iphoneos", "iphonesimulator") and
on the version of Xcode.
"""
macros and implementing filtering by VALID_ARCHS. The expansion of macros
depends on the SDKROOT used ("macosx", "iphoneos", "iphonesimulator") and
on the version of Xcode.
"""
# Match variable like $(ARCHS_STANDARD).
variable_pattern = re.compile(r"\$\([a-zA-Z_][a-zA-Z0-9_]*\)$")
@ -81,8 +81,8 @@ class XcodeArchsDefault:
def ActiveArchs(self, archs, valid_archs, sdkroot):
"""Expands variables references in ARCHS, and filter by VALID_ARCHS if it
is defined (if not set, Xcode accept any value in ARCHS, otherwise, only
values present in VALID_ARCHS are kept)."""
is defined (if not set, Xcode accept any value in ARCHS, otherwise, only
values present in VALID_ARCHS are kept)."""
expanded_archs = self._ExpandArchs(archs or self._default, sdkroot or "")
if valid_archs:
filtered_archs = []
@ -95,24 +95,24 @@ class XcodeArchsDefault:
def GetXcodeArchsDefault():
"""Returns the |XcodeArchsDefault| object to use to expand ARCHS for the
installed version of Xcode. The default values used by Xcode for ARCHS
and the expansion of the variables depends on the version of Xcode used.
installed version of Xcode. The default values used by Xcode for ARCHS
and the expansion of the variables depends on the version of Xcode used.
For all version anterior to Xcode 5.0 or posterior to Xcode 5.1 included
uses $(ARCHS_STANDARD) if ARCHS is unset, while Xcode 5.0 to 5.0.2 uses
$(ARCHS_STANDARD_INCLUDING_64_BIT). This variable was added to Xcode 5.0
and deprecated with Xcode 5.1.
For all version anterior to Xcode 5.0 or posterior to Xcode 5.1 included
uses $(ARCHS_STANDARD) if ARCHS is unset, while Xcode 5.0 to 5.0.2 uses
$(ARCHS_STANDARD_INCLUDING_64_BIT). This variable was added to Xcode 5.0
and deprecated with Xcode 5.1.
For "macosx" SDKROOT, all version starting with Xcode 5.0 includes 64-bit
architecture as part of $(ARCHS_STANDARD) and default to only building it.
For "macosx" SDKROOT, all version starting with Xcode 5.0 includes 64-bit
architecture as part of $(ARCHS_STANDARD) and default to only building it.
For "iphoneos" and "iphonesimulator" SDKROOT, 64-bit architectures are part
of $(ARCHS_STANDARD_INCLUDING_64_BIT) from Xcode 5.0. From Xcode 5.1, they
are also part of $(ARCHS_STANDARD).
For "iphoneos" and "iphonesimulator" SDKROOT, 64-bit architectures are part
of $(ARCHS_STANDARD_INCLUDING_64_BIT) from Xcode 5.0. From Xcode 5.1, they
are also part of $(ARCHS_STANDARD).
All these rules are coded in the construction of the |XcodeArchsDefault|
object to use depending on the version of Xcode detected. The object is
for performance reason."""
All these rules are coded in the construction of the |XcodeArchsDefault|
object to use depending on the version of Xcode detected. The object is
for performance reason."""
global XCODE_ARCHS_DEFAULT_CACHE
if XCODE_ARCHS_DEFAULT_CACHE:
return XCODE_ARCHS_DEFAULT_CACHE
@ -189,8 +189,8 @@ class XcodeSettings:
def _ConvertConditionalKeys(self, configname):
"""Converts or warns on conditional keys. Xcode supports conditional keys,
such as CODE_SIGN_IDENTITY[sdk=iphoneos*]. This is a partial implementation
with some keys converted while the rest force a warning."""
such as CODE_SIGN_IDENTITY[sdk=iphoneos*]. This is a partial implementation
with some keys converted while the rest force a warning."""
settings = self.xcode_settings[configname]
conditional_keys = [key for key in settings if key.endswith("]")]
for key in conditional_keys:
@ -255,13 +255,13 @@ class XcodeSettings:
def GetFrameworkVersion(self):
"""Returns the framework version of the current target. Only valid for
bundles."""
bundles."""
assert self._IsBundle()
return self.GetPerTargetSetting("FRAMEWORK_VERSION", default="A")
def GetWrapperExtension(self):
"""Returns the bundle extension (.app, .framework, .plugin, etc). Only
valid for bundles."""
valid for bundles."""
assert self._IsBundle()
if self.spec["type"] in ("loadable_module", "shared_library"):
default_wrapper_extension = {
@ -296,13 +296,13 @@ class XcodeSettings:
def GetWrapperName(self):
"""Returns the directory name of the bundle represented by this target.
Only valid for bundles."""
Only valid for bundles."""
assert self._IsBundle()
return self.GetProductName() + self.GetWrapperExtension()
def GetBundleContentsFolderPath(self):
"""Returns the qualified path to the bundle's contents folder. E.g.
Chromium.app/Contents or Foo.bundle/Versions/A. Only valid for bundles."""
Chromium.app/Contents or Foo.bundle/Versions/A. Only valid for bundles."""
if self.isIOS:
return self.GetWrapperName()
assert self._IsBundle()
@ -316,7 +316,7 @@ class XcodeSettings:
def GetBundleResourceFolder(self):
"""Returns the qualified path to the bundle's resource folder. E.g.
Chromium.app/Contents/Resources. Only valid for bundles."""
Chromium.app/Contents/Resources. Only valid for bundles."""
assert self._IsBundle()
if self.isIOS:
return self.GetBundleContentsFolderPath()
@ -324,7 +324,7 @@ class XcodeSettings:
def GetBundleExecutableFolderPath(self):
"""Returns the qualified path to the bundle's executables folder. E.g.
Chromium.app/Contents/MacOS. Only valid for bundles."""
Chromium.app/Contents/MacOS. Only valid for bundles."""
assert self._IsBundle()
if self.spec["type"] in ("shared_library") or self.isIOS:
return self.GetBundleContentsFolderPath()
@ -333,25 +333,25 @@ class XcodeSettings:
def GetBundleJavaFolderPath(self):
"""Returns the qualified path to the bundle's Java resource folder.
E.g. Chromium.app/Contents/Resources/Java. Only valid for bundles."""
E.g. Chromium.app/Contents/Resources/Java. Only valid for bundles."""
assert self._IsBundle()
return os.path.join(self.GetBundleResourceFolder(), "Java")
def GetBundleFrameworksFolderPath(self):
"""Returns the qualified path to the bundle's frameworks folder. E.g,
Chromium.app/Contents/Frameworks. Only valid for bundles."""
Chromium.app/Contents/Frameworks. Only valid for bundles."""
assert self._IsBundle()
return os.path.join(self.GetBundleContentsFolderPath(), "Frameworks")
def GetBundleSharedFrameworksFolderPath(self):
"""Returns the qualified path to the bundle's frameworks folder. E.g,
Chromium.app/Contents/SharedFrameworks. Only valid for bundles."""
Chromium.app/Contents/SharedFrameworks. Only valid for bundles."""
assert self._IsBundle()
return os.path.join(self.GetBundleContentsFolderPath(), "SharedFrameworks")
def GetBundleSharedSupportFolderPath(self):
"""Returns the qualified path to the bundle's shared support folder. E.g,
Chromium.app/Contents/SharedSupport. Only valid for bundles."""
Chromium.app/Contents/SharedSupport. Only valid for bundles."""
assert self._IsBundle()
if self.spec["type"] == "shared_library":
return self.GetBundleResourceFolder()
@ -360,19 +360,19 @@ class XcodeSettings:
def GetBundlePlugInsFolderPath(self):
"""Returns the qualified path to the bundle's plugins folder. E.g,
Chromium.app/Contents/PlugIns. Only valid for bundles."""
Chromium.app/Contents/PlugIns. Only valid for bundles."""
assert self._IsBundle()
return os.path.join(self.GetBundleContentsFolderPath(), "PlugIns")
def GetBundleXPCServicesFolderPath(self):
"""Returns the qualified path to the bundle's XPC services folder. E.g,
Chromium.app/Contents/XPCServices. Only valid for bundles."""
Chromium.app/Contents/XPCServices. Only valid for bundles."""
assert self._IsBundle()
return os.path.join(self.GetBundleContentsFolderPath(), "XPCServices")
def GetBundlePlistPath(self):
"""Returns the qualified path to the bundle's plist file. E.g.
Chromium.app/Contents/Info.plist. Only valid for bundles."""
Chromium.app/Contents/Info.plist. Only valid for bundles."""
assert self._IsBundle()
if (
self.spec["type"] in ("executable", "loadable_module")
@ -438,7 +438,7 @@ class XcodeSettings:
def _GetBundleBinaryPath(self):
"""Returns the name of the bundle binary of by this target.
E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles."""
E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles."""
assert self._IsBundle()
return os.path.join(
self.GetBundleExecutableFolderPath(), self.GetExecutableName()
@ -469,19 +469,16 @@ class XcodeSettings:
def _GetStandaloneBinaryPath(self):
"""Returns the name of the non-bundle binary represented by this target.
E.g. hello_world. Only valid for non-bundles."""
E.g. hello_world. Only valid for non-bundles."""
assert not self._IsBundle()
assert self.spec["type"] in (
assert self.spec["type"] in {
"executable",
"shared_library",
"static_library",
"loadable_module",
), ("Unexpected type %s" % self.spec["type"])
}, "Unexpected type %s" % self.spec["type"]
target = self.spec["target_name"]
if self.spec["type"] == "static_library":
if target[:3] == "lib":
target = target[3:]
elif self.spec["type"] in ("loadable_module", "shared_library"):
if self.spec["type"] in {"loadable_module", "shared_library", "static_library"}:
if target[:3] == "lib":
target = target[3:]
@ -492,7 +489,7 @@ class XcodeSettings:
def GetExecutableName(self):
"""Returns the executable name of the bundle represented by this target.
E.g. Chromium."""
E.g. Chromium."""
if self._IsBundle():
return self.spec.get("product_name", self.spec["target_name"])
else:
@ -500,7 +497,7 @@ class XcodeSettings:
def GetExecutablePath(self):
"""Returns the qualified path to the primary executable of the bundle
represented by this target. E.g. Chromium.app/Contents/MacOS/Chromium."""
represented by this target. E.g. Chromium.app/Contents/MacOS/Chromium."""
if self._IsBundle():
return self._GetBundleBinaryPath()
else:
@ -523,7 +520,7 @@ class XcodeSettings:
# most sensible route and should still do the right thing.
try:
return GetStdoutQuiet(["xcrun", "--sdk", sdk, infoitem])
except GypError:
except (GypError, OSError):
pass
def _SdkRoot(self, configname):
@ -570,7 +567,7 @@ class XcodeSettings:
def GetCflags(self, configname, arch=None):
"""Returns flags that need to be added to .c, .cc, .m, and .mm
compilations."""
compilations."""
# This functions (and the similar ones below) do not offer complete
# emulation of all xcode_settings keys. They're implemented on demand.
@ -579,7 +576,8 @@ class XcodeSettings:
sdk_root = self._SdkPath()
if "SDKROOT" in self._Settings() and sdk_root:
cflags.append("-isysroot %s" % sdk_root)
cflags.append("-isysroot")
cflags.append(sdk_root)
if self.header_map_path:
cflags.append("-I%s" % self.header_map_path)
@ -664,7 +662,8 @@ class XcodeSettings:
# TODO: Supporting fat binaries will be annoying.
self._WarnUnimplemented("ARCHS")
archs = ["i386"]
cflags.append("-arch " + archs[0])
cflags.append("-arch")
cflags.append(archs[0])
if archs[0] in ("i386", "x86_64"):
if self._Test("GCC_ENABLE_SSE3_EXTENSIONS", "YES", default="NO"):
@ -685,10 +684,7 @@ class XcodeSettings:
if platform_root:
cflags.append("-F" + platform_root + "/Developer/Library/Frameworks/")
if sdk_root:
framework_root = sdk_root
else:
framework_root = ""
framework_root = sdk_root if sdk_root else ""
config = self.spec["configurations"][self.configname]
framework_dirs = config.get("mac_framework_dirs", [])
for directory in framework_dirs:
@ -866,7 +862,7 @@ class XcodeSettings:
def _MapLinkerFlagFilename(self, ldflag, gyp_to_build_path):
"""Checks if ldflag contains a filename and if so remaps it from
gyp-directory-relative to build-directory-relative."""
gyp-directory-relative to build-directory-relative."""
# This list is expanded on demand.
# They get matched as:
# -exported_symbols_list file
@ -898,13 +894,13 @@ class XcodeSettings:
def GetLdflags(self, configname, product_dir, gyp_to_build_path, arch=None):
"""Returns flags that need to be passed to the linker.
Args:
configname: The name of the configuration to get ld flags for.
product_dir: The directory where products such static and dynamic
libraries are placed. This is added to the library search path.
gyp_to_build_path: A function that converts paths relative to the
current gyp file to paths relative to the build directory.
"""
Args:
configname: The name of the configuration to get ld flags for.
product_dir: The directory where products such static and dynamic
libraries are placed. This is added to the library search path.
gyp_to_build_path: A function that converts paths relative to the
current gyp file to paths relative to the build directory.
"""
self.configname = configname
ldflags = []
@ -927,17 +923,15 @@ class XcodeSettings:
self._AppendPlatformVersionMinFlags(ldflags)
if "SDKROOT" in self._Settings() and self._SdkPath():
ldflags.append("-isysroot " + self._SdkPath())
ldflags.append("-isysroot")
ldflags.append(self._SdkPath())
for library_path in self._Settings().get("LIBRARY_SEARCH_PATHS", []):
ldflags.append("-L" + gyp_to_build_path(library_path))
if "ORDER_FILE" in self._Settings():
ldflags.append(
"-Wl,-order_file "
+ "-Wl,"
+ gyp_to_build_path(self._Settings()["ORDER_FILE"])
)
ldflags.append("-Wl,-order_file")
ldflags.append("-Wl," + gyp_to_build_path(self._Settings()["ORDER_FILE"]))
if not gyp.common.CrossCompileRequested():
if arch is not None:
@ -949,7 +943,9 @@ class XcodeSettings:
# TODO: Supporting fat binaries will be annoying.
self._WarnUnimplemented("ARCHS")
archs = ["i386"]
ldflags.append("-arch " + archs[0])
# Avoid quoting the space between -arch and the arch name
ldflags.append("-arch")
ldflags.append(archs[0])
# Xcode adds the product directory by default.
# Rewrite -L. to -L./ to work around http://www.openradar.me/25313838
@ -957,7 +953,8 @@ class XcodeSettings:
install_name = self.GetInstallName()
if install_name and self.spec["type"] != "loadable_module":
ldflags.append("-install_name " + install_name.replace(" ", r"\ "))
ldflags.append("-install_name")
ldflags.append(install_name.replace(" ", r"\ "))
for rpath in self._Settings().get("LD_RUNPATH_SEARCH_PATHS", []):
ldflags.append("-Wl,-rpath," + rpath)
@ -974,7 +971,8 @@ class XcodeSettings:
platform_root = self._XcodePlatformPath(configname)
if sdk_root and platform_root:
ldflags.append("-F" + platform_root + "/Developer/Library/Frameworks/")
ldflags.append("-framework XCTest")
ldflags.append("-framework")
ldflags.append("XCTest")
is_extension = self._IsIosAppExtension() or self._IsIosWatchKitExtension()
if sdk_root and is_extension:
@ -990,7 +988,8 @@ class XcodeSettings:
+ "/System/Library/PrivateFrameworks/PlugInKit.framework/PlugInKit"
)
else:
ldflags.append("-e _NSExtensionMain")
ldflags.append("-e")
ldflags.append("_NSExtensionMain")
ldflags.append("-fapplication-extension")
self._Appendf(ldflags, "CLANG_CXX_LIBRARY", "-stdlib=%s")
@ -1001,9 +1000,9 @@ class XcodeSettings:
def GetLibtoolflags(self, configname):
"""Returns flags that need to be passed to the static linker.
Args:
configname: The name of the configuration to get ld flags for.
"""
Args:
configname: The name of the configuration to get ld flags for.
"""
self.configname = configname
libtoolflags = []
@ -1016,7 +1015,7 @@ class XcodeSettings:
def GetPerTargetSettings(self):
"""Gets a list of all the per-target settings. This will only fetch keys
whose values are the same across all configurations."""
whose values are the same across all configurations."""
first_pass = True
result = {}
for configname in sorted(self.xcode_settings.keys()):
@ -1039,7 +1038,7 @@ class XcodeSettings:
def GetPerTargetSetting(self, setting, default=None):
"""Tries to get xcode_settings.setting from spec. Assumes that the setting
has the same value in all configurations and throws otherwise."""
has the same value in all configurations and throws otherwise."""
is_first_pass = True
result = None
for configname in sorted(self.xcode_settings.keys()):
@ -1057,15 +1056,14 @@ class XcodeSettings:
def _GetStripPostbuilds(self, configname, output_binary, quiet):
"""Returns a list of shell commands that contain the shell commands
necessary to strip this target's binary. These should be run as postbuilds
before the actual postbuilds run."""
necessary to strip this target's binary. These should be run as postbuilds
before the actual postbuilds run."""
self.configname = configname
result = []
if self._Test("DEPLOYMENT_POSTPROCESSING", "YES", default="NO") and self._Test(
"STRIP_INSTALLED_PRODUCT", "YES", default="NO"
):
default_strip_style = "debugging"
if (
self.spec["type"] == "loadable_module" or self._IsIosAppExtension()
@ -1092,8 +1090,8 @@ class XcodeSettings:
def _GetDebugInfoPostbuilds(self, configname, output, output_binary, quiet):
"""Returns a list of shell commands that contain the shell commands
necessary to massage this target's debug information. These should be run
as postbuilds before the actual postbuilds run."""
necessary to massage this target's debug information. These should be run
as postbuilds before the actual postbuilds run."""
self.configname = configname
# For static libraries, no dSYMs are created.
@ -1114,7 +1112,7 @@ class XcodeSettings:
def _GetTargetPostbuilds(self, configname, output, output_binary, quiet=False):
"""Returns a list of shell commands that contain the shell commands
to run as postbuilds for this target, before the actual postbuilds."""
to run as postbuilds for this target, before the actual postbuilds."""
# dSYMs need to build before stripping happens.
return self._GetDebugInfoPostbuilds(
configname, output, output_binary, quiet
@ -1122,11 +1120,10 @@ class XcodeSettings:
def _GetIOSPostbuilds(self, configname, output_binary):
"""Return a shell command to codesign the iOS output binary so it can
be deployed to a device. This should be run as the very last step of the
build."""
be deployed to a device. This should be run as the very last step of the
build."""
if not (
self.isIOS
and (self.spec["type"] == "executable" or self._IsXCTest())
(self.isIOS and (self.spec["type"] == "executable" or self._IsXCTest()))
or self.IsIosFramework()
):
return []
@ -1172,8 +1169,9 @@ class XcodeSettings:
# Then re-sign everything with 'preserve=True'
postbuilds.extend(
[
'%s code-sign-bundle "%s" "%s" "%s" "%s" %s'
'%s %s code-sign-bundle "%s" "%s" "%s" "%s" %s'
% (
sys.executable,
os.path.join("${TARGET_BUILD_DIR}", "gyp-mac-tool"),
key,
settings.get("CODE_SIGN_ENTITLEMENTS", ""),
@ -1188,8 +1186,9 @@ class XcodeSettings:
for target in targets:
postbuilds.extend(
[
'%s code-sign-bundle "%s" "%s" "%s" "%s" %s'
'%s %s code-sign-bundle "%s" "%s" "%s" "%s" %s'
% (
sys.executable,
os.path.join("${TARGET_BUILD_DIR}", "gyp-mac-tool"),
key,
settings.get("CODE_SIGN_ENTITLEMENTS", ""),
@ -1202,8 +1201,9 @@ class XcodeSettings:
postbuilds.extend(
[
'%s code-sign-bundle "%s" "%s" "%s" "%s" %s'
'%s %s code-sign-bundle "%s" "%s" "%s" "%s" %s'
% (
sys.executable,
os.path.join("${TARGET_BUILD_DIR}", "gyp-mac-tool"),
key,
settings.get("CODE_SIGN_ENTITLEMENTS", ""),
@ -1237,7 +1237,7 @@ class XcodeSettings:
self, configname, output, output_binary, postbuilds=[], quiet=False
):
"""Returns a list of shell commands that should run before and after
|postbuilds|."""
|postbuilds|."""
assert output_binary is not None
pre = self._GetTargetPostbuilds(configname, output, output_binary, quiet)
post = self._GetIOSPostbuilds(configname, output_binary)
@ -1248,10 +1248,7 @@ class XcodeSettings:
l_flag = "-framework " + os.path.splitext(os.path.basename(library))[0]
else:
m = self.library_re.match(library)
if m:
l_flag = "-l" + m.group(1)
else:
l_flag = library
l_flag = "-l" + m.group(1) if m else library
sdk_root = self._SdkPath(config_name)
if not sdk_root:
@ -1276,8 +1273,8 @@ class XcodeSettings:
def AdjustLibraries(self, libraries, config_name=None):
"""Transforms entries like 'Cocoa.framework' in libraries into entries like
'-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc.
"""
'-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc.
"""
libraries = [self._AdjustLibrary(library, config_name) for library in libraries]
return libraries
@ -1342,20 +1339,19 @@ class XcodeSettings:
def _DefaultSdkRoot(self):
"""Returns the default SDKROOT to use.
Prior to version 5.0.0, if SDKROOT was not explicitly set in the Xcode
project, then the environment variable was empty. Starting with this
version, Xcode uses the name of the newest SDK installed.
"""
Prior to version 5.0.0, if SDKROOT was not explicitly set in the Xcode
project, then the environment variable was empty. Starting with this
version, Xcode uses the name of the newest SDK installed.
"""
xcode_version, _ = XcodeVersion()
if xcode_version < "0500":
return ""
default_sdk_path = self._XcodeSdkPath("")
default_sdk_root = XcodeSettings._sdk_root_cache.get(default_sdk_path)
if default_sdk_root:
if default_sdk_root := XcodeSettings._sdk_root_cache.get(default_sdk_path):
return default_sdk_root
try:
all_sdks = GetStdout(["xcodebuild", "-showsdks"])
except GypError:
except (GypError, OSError):
# If xcodebuild fails, there will be no valid SDKs
return ""
for line in all_sdks.splitlines():
@ -1371,39 +1367,39 @@ class XcodeSettings:
class MacPrefixHeader:
"""A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature.
This feature consists of several pieces:
* If GCC_PREFIX_HEADER is present, all compilations in that project get an
additional |-include path_to_prefix_header| cflag.
* If GCC_PRECOMPILE_PREFIX_HEADER is present too, then the prefix header is
instead compiled, and all other compilations in the project get an
additional |-include path_to_compiled_header| instead.
+ Compiled prefix headers have the extension gch. There is one gch file for
every language used in the project (c, cc, m, mm), since gch files for
different languages aren't compatible.
+ gch files themselves are built with the target's normal cflags, but they
obviously don't get the |-include| flag. Instead, they need a -x flag that
describes their language.
+ All o files in the target need to depend on the gch file, to make sure
it's built before any o file is built.
This feature consists of several pieces:
* If GCC_PREFIX_HEADER is present, all compilations in that project get an
additional |-include path_to_prefix_header| cflag.
* If GCC_PRECOMPILE_PREFIX_HEADER is present too, then the prefix header is
instead compiled, and all other compilations in the project get an
additional |-include path_to_compiled_header| instead.
+ Compiled prefix headers have the extension gch. There is one gch file for
every language used in the project (c, cc, m, mm), since gch files for
different languages aren't compatible.
+ gch files themselves are built with the target's normal cflags, but they
obviously don't get the |-include| flag. Instead, they need a -x flag that
describes their language.
+ All o files in the target need to depend on the gch file, to make sure
it's built before any o file is built.
This class helps with some of these tasks, but it needs help from the build
system for writing dependencies to the gch files, for writing build commands
for the gch files, and for figuring out the location of the gch files.
"""
This class helps with some of these tasks, but it needs help from the build
system for writing dependencies to the gch files, for writing build commands
for the gch files, and for figuring out the location of the gch files.
"""
def __init__(
self, xcode_settings, gyp_path_to_build_path, gyp_path_to_build_output
):
"""If xcode_settings is None, all methods on this class are no-ops.
Args:
gyp_path_to_build_path: A function that takes a gyp-relative path,
and returns a path relative to the build directory.
gyp_path_to_build_output: A function that takes a gyp-relative path and
a language code ('c', 'cc', 'm', or 'mm'), and that returns a path
to where the output of precompiling that path for that language
should be placed (without the trailing '.gch').
"""
Args:
gyp_path_to_build_path: A function that takes a gyp-relative path,
and returns a path relative to the build directory.
gyp_path_to_build_output: A function that takes a gyp-relative path and
a language code ('c', 'cc', 'm', or 'mm'), and that returns a path
to where the output of precompiling that path for that language
should be placed (without the trailing '.gch').
"""
# This doesn't support per-configuration prefix headers. Good enough
# for now.
self.header = None
@ -1448,9 +1444,9 @@ class MacPrefixHeader:
def GetObjDependencies(self, sources, objs, arch=None):
"""Given a list of source files and the corresponding object files, returns
a list of (source, object, gch) tuples, where |gch| is the build-directory
relative path to the gch file each object file depends on. |compilable[i]|
has to be the source file belonging to |objs[i]|."""
a list of (source, object, gch) tuples, where |gch| is the build-directory
relative path to the gch file each object file depends on. |compilable[i]|
has to be the source file belonging to |objs[i]|."""
if not self.header or not self.compile_headers:
return []
@ -1471,8 +1467,8 @@ class MacPrefixHeader:
def GetPchBuildCommands(self, arch=None):
"""Returns [(path_to_gch, language_flag, language, header)].
|path_to_gch| and |header| are relative to the build directory.
"""
|path_to_gch| and |header| are relative to the build directory.
"""
if not self.header or not self.compile_headers:
return []
return [
@ -1509,7 +1505,8 @@ def XcodeVersion():
raise GypError("xcodebuild returned unexpected results")
version = version_list[0].split()[-1] # Last word on first line
build = version_list[-1].split()[-1] # Last word on last line
except GypError: # Xcode not installed so look for XCode Command Line Tools
except (GypError, OSError):
# Xcode not installed so look for XCode Command Line Tools
version = CLTVersion() # macOS Catalina returns 11.0.0.0.1.1567737322
if not version:
raise GypError("No Xcode or CLT version detected!")
@ -1537,26 +1534,28 @@ def CLTVersion():
FROM_XCODE_PKG_ID = "com.apple.pkg.DeveloperToolsCLI"
MAVERICKS_PKG_ID = "com.apple.pkg.CLTools_Executables"
regex = re.compile("version: (?P<version>.+)")
regex = re.compile(r"version: (?P<version>.+)")
for key in [MAVERICKS_PKG_ID, STANDALONE_PKG_ID, FROM_XCODE_PKG_ID]:
try:
output = GetStdout(["/usr/sbin/pkgutil", "--pkg-info", key])
return re.search(regex, output).groupdict()["version"]
except GypError:
if m := re.search(regex, output):
return m.groupdict()["version"]
except (GypError, OSError):
continue
regex = re.compile(r'Command Line Tools for Xcode\s+(?P<version>\S+)')
regex = re.compile(r"Command Line Tools for Xcode\s+(?P<version>\S+)")
try:
output = GetStdout(["/usr/sbin/softwareupdate", "--history"])
return re.search(regex, output).groupdict()["version"]
except GypError:
if m := re.search(regex, output):
return m.groupdict()["version"]
except (GypError, OSError):
return None
def GetStdoutQuiet(cmdlist):
"""Returns the content of standard output returned by invoking |cmdlist|.
Ignores the stderr.
Raises |GypError| if the command return with a non-zero return code."""
Ignores the stderr.
Raises |GypError| if the command return with a non-zero return code."""
job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out = job.communicate()[0].decode("utf-8")
if job.returncode != 0:
@ -1566,7 +1565,7 @@ def GetStdoutQuiet(cmdlist):
def GetStdout(cmdlist):
"""Returns the content of standard output returned by invoking |cmdlist|.
Raises |GypError| if the command return with a non-zero return code."""
Raises |GypError| if the command return with a non-zero return code."""
job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE)
out = job.communicate()[0].decode("utf-8")
if job.returncode != 0:
@ -1577,9 +1576,9 @@ def GetStdout(cmdlist):
def MergeGlobalXcodeSettingsToSpec(global_dict, spec):
"""Merges the global xcode_settings dictionary into each configuration of the
target represented by spec. For keys that are both in the global and the local
xcode_settings dict, the local key gets precedence.
"""
target represented by spec. For keys that are both in the global and the local
xcode_settings dict, the local key gets precedence.
"""
# The xcode generator special-cases global xcode_settings and does something
# that amounts to merging in the global xcode_settings into each local
# xcode_settings dict.
@ -1594,9 +1593,9 @@ def MergeGlobalXcodeSettingsToSpec(global_dict, spec):
def IsMacBundle(flavor, spec):
"""Returns if |spec| should be treated as a bundle.
Bundles are directories with a certain subdirectory structure, instead of
just a single file. Bundle rules do not produce a binary but also package
resources into that directory."""
Bundles are directories with a certain subdirectory structure, instead of
just a single file. Bundle rules do not produce a binary but also package
resources into that directory."""
is_mac_bundle = (
int(spec.get("mac_xctest_bundle", 0)) != 0
or int(spec.get("mac_xcuitest_bundle", 0)) != 0
@ -1613,14 +1612,14 @@ def IsMacBundle(flavor, spec):
def GetMacBundleResources(product_dir, xcode_settings, resources):
"""Yields (output, resource) pairs for every resource in |resources|.
Only call this for mac bundle targets.
Only call this for mac bundle targets.
Args:
product_dir: Path to the directory containing the output bundle,
relative to the build directory.
xcode_settings: The XcodeSettings of the current target.
resources: A list of bundle resources, relative to the build directory.
"""
Args:
product_dir: Path to the directory containing the output bundle,
relative to the build directory.
xcode_settings: The XcodeSettings of the current target.
resources: A list of bundle resources, relative to the build directory.
"""
dest = os.path.join(product_dir, xcode_settings.GetBundleResourceFolder())
for res in resources:
output = dest
@ -1651,24 +1650,24 @@ def GetMacBundleResources(product_dir, xcode_settings, resources):
def GetMacInfoPlist(product_dir, xcode_settings, gyp_path_to_build_path):
"""Returns (info_plist, dest_plist, defines, extra_env), where:
* |info_plist| is the source plist path, relative to the
build directory,
* |dest_plist| is the destination plist path, relative to the
build directory,
* |defines| is a list of preprocessor defines (empty if the plist
shouldn't be preprocessed,
* |extra_env| is a dict of env variables that should be exported when
invoking |mac_tool copy-info-plist|.
* |info_plist| is the source plist path, relative to the
build directory,
* |dest_plist| is the destination plist path, relative to the
build directory,
* |defines| is a list of preprocessor defines (empty if the plist
shouldn't be preprocessed,
* |extra_env| is a dict of env variables that should be exported when
invoking |mac_tool copy-info-plist|.
Only call this for mac bundle targets.
Only call this for mac bundle targets.
Args:
product_dir: Path to the directory containing the output bundle,
relative to the build directory.
xcode_settings: The XcodeSettings of the current target.
gyp_to_build_path: A function that converts paths relative to the
current gyp file to paths relative to the build directory.
"""
Args:
product_dir: Path to the directory containing the output bundle,
relative to the build directory.
xcode_settings: The XcodeSettings of the current target.
gyp_to_build_path: A function that converts paths relative to the
current gyp file to paths relative to the build directory.
"""
info_plist = xcode_settings.GetPerTargetSetting("INFOPLIST_FILE")
if not info_plist:
return None, None, [], {}
@ -1706,18 +1705,18 @@ def _GetXcodeEnv(
xcode_settings, built_products_dir, srcroot, configuration, additional_settings=None
):
"""Return the environment variables that Xcode would set. See
http://developer.apple.com/library/mac/#documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW153
for a full list.
http://developer.apple.com/library/mac/#documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW153
for a full list.
Args:
xcode_settings: An XcodeSettings object. If this is None, this function
returns an empty dict.
built_products_dir: Absolute path to the built products dir.
srcroot: Absolute path to the source root.
configuration: The build configuration name.
additional_settings: An optional dict with more values to add to the
result.
"""
Args:
xcode_settings: An XcodeSettings object. If this is None, this function
returns an empty dict.
built_products_dir: Absolute path to the built products dir.
srcroot: Absolute path to the source root.
configuration: The build configuration name.
additional_settings: An optional dict with more values to add to the
result.
"""
if not xcode_settings:
return {}
@ -1771,27 +1770,25 @@ def _GetXcodeEnv(
)
env["CONTENTS_FOLDER_PATH"] = xcode_settings.GetBundleContentsFolderPath()
env["EXECUTABLE_FOLDER_PATH"] = xcode_settings.GetBundleExecutableFolderPath()
env[
"UNLOCALIZED_RESOURCES_FOLDER_PATH"
] = xcode_settings.GetBundleResourceFolder()
env["UNLOCALIZED_RESOURCES_FOLDER_PATH"] = (
xcode_settings.GetBundleResourceFolder()
)
env["JAVA_FOLDER_PATH"] = xcode_settings.GetBundleJavaFolderPath()
env["FRAMEWORKS_FOLDER_PATH"] = xcode_settings.GetBundleFrameworksFolderPath()
env[
"SHARED_FRAMEWORKS_FOLDER_PATH"
] = xcode_settings.GetBundleSharedFrameworksFolderPath()
env[
"SHARED_SUPPORT_FOLDER_PATH"
] = xcode_settings.GetBundleSharedSupportFolderPath()
env["SHARED_FRAMEWORKS_FOLDER_PATH"] = (
xcode_settings.GetBundleSharedFrameworksFolderPath()
)
env["SHARED_SUPPORT_FOLDER_PATH"] = (
xcode_settings.GetBundleSharedSupportFolderPath()
)
env["PLUGINS_FOLDER_PATH"] = xcode_settings.GetBundlePlugInsFolderPath()
env["XPCSERVICES_FOLDER_PATH"] = xcode_settings.GetBundleXPCServicesFolderPath()
env["INFOPLIST_PATH"] = xcode_settings.GetBundlePlistPath()
env["WRAPPER_NAME"] = xcode_settings.GetWrapperName()
install_name = xcode_settings.GetInstallName()
if install_name:
if install_name := xcode_settings.GetInstallName():
env["LD_DYLIB_INSTALL_NAME"] = install_name
install_name_base = xcode_settings.GetInstallNameBase()
if install_name_base:
if install_name_base := xcode_settings.GetInstallNameBase():
env["DYLIB_INSTALL_NAME_BASE"] = install_name_base
xcode_version, _ = XcodeVersion()
if xcode_version >= "0500" and not env.get("SDKROOT"):
@ -1819,8 +1816,8 @@ def _GetXcodeEnv(
def _NormalizeEnvVarReferences(str):
"""Takes a string containing variable references in the form ${FOO}, $(FOO),
or $FOO, and returns a string with all variable references in the form ${FOO}.
"""
or $FOO, and returns a string with all variable references in the form ${FOO}.
"""
# $FOO -> ${FOO}
str = re.sub(r"\$([a-zA-Z_][a-zA-Z0-9_]*)", r"${\1}", str)
@ -1836,9 +1833,9 @@ def _NormalizeEnvVarReferences(str):
def ExpandEnvVars(string, expansions):
"""Expands ${VARIABLES}, $(VARIABLES), and $VARIABLES in string per the
expansions list. If the variable expands to something that references
another variable, this variable is expanded as well if it's in env --
until no variables present in env are left."""
expansions list. If the variable expands to something that references
another variable, this variable is expanded as well if it's in env --
until no variables present in env are left."""
for k, v in reversed(expansions):
string = string.replace("${" + k + "}", v)
string = string.replace("$(" + k + ")", v)
@ -1848,18 +1845,18 @@ def ExpandEnvVars(string, expansions):
def _TopologicallySortedEnvVarKeys(env):
"""Takes a dict |env| whose values are strings that can refer to other keys,
for example env['foo'] = '$(bar) and $(baz)'. Returns a list L of all keys of
env such that key2 is after key1 in L if env[key2] refers to env[key1].
for example env['foo'] = '$(bar) and $(baz)'. Returns a list L of all keys of
env such that key2 is after key1 in L if env[key2] refers to env[key1].
Throws an Exception in case of dependency cycles.
"""
Throws an Exception in case of dependency cycles.
"""
# Since environment variables can refer to other variables, the evaluation
# order is important. Below is the logic to compute the dependency graph
# and sort it.
regex = re.compile(r"\$\{([a-zA-Z0-9\-_]+)\}")
def GetEdges(node):
# Use a definition of edges such that user_of_variable -> used_varible.
# Use a definition of edges such that user_of_variable -> used_variable.
# This happens to be easier in this case, since a variable's
# definition contains all variables it references in a single string.
# We can then reverse the result of the topological sort at the end.
@ -1893,7 +1890,7 @@ def GetSortedXcodeEnv(
def GetSpecPostbuildCommands(spec, quiet=False):
"""Returns the list of postbuilds explicitly defined on |spec|, in a form
executable by a shell."""
executable by a shell."""
postbuilds = []
for postbuild in spec.get("postbuilds", []):
if not quiet:
@ -1907,7 +1904,7 @@ def GetSpecPostbuildCommands(spec, quiet=False):
def _HasIOSTarget(targets):
"""Returns true if any target contains the iOS specific key
IPHONEOS_DEPLOYMENT_TARGET."""
IPHONEOS_DEPLOYMENT_TARGET."""
for target_dict in targets.values():
for config in target_dict["configurations"].values():
if config.get("xcode_settings", {}).get("IPHONEOS_DEPLOYMENT_TARGET"):
@ -1917,7 +1914,7 @@ def _HasIOSTarget(targets):
def _AddIOSDeviceConfigurations(targets):
"""Clone all targets and append -iphoneos to the name. Configure these targets
to build for iOS devices and use correct architectures for those builds."""
to build for iOS devices and use correct architectures for those builds."""
for target_dict in targets.values():
toolset = target_dict["toolset"]
configs = target_dict["configurations"]
@ -1933,7 +1930,7 @@ def _AddIOSDeviceConfigurations(targets):
def CloneConfigurationForDeviceAndEmulator(target_dicts):
"""If |target_dicts| contains any iOS targets, automatically create -iphoneos
targets for iOS device builds."""
targets for iOS device builds."""
if _HasIOSTarget(target_dicts):
return _AddIOSDeviceConfigurations(target_dicts)
return target_dicts

View file

@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Unit tests for the xcode_emulation.py file."""
import sys
import unittest
from gyp.xcode_emulation import XcodeSettings
class TestXcodeSettings(unittest.TestCase):
def setUp(self):
if sys.platform != "darwin":
self.skipTest("This test only runs on macOS")
def test_GetCflags(self):
target = {
"type": "static_library",
"configurations": {
"Release": {},
},
}
configuration_name = "Release"
xcode_settings = XcodeSettings(target)
cflags = xcode_settings.GetCflags(configuration_name, "arm64")
# Do not quote `-arch arm64` with spaces in one string.
self.assertEqual(
cflags,
["-fasm-blocks", "-mpascal-strings", "-Os", "-gdwarf-2", "-arch", "arm64"],
)
def GypToBuildPath(self, path):
return path
def test_GetLdflags(self):
target = {
"type": "static_library",
"configurations": {
"Release": {},
},
}
configuration_name = "Release"
xcode_settings = XcodeSettings(target)
ldflags = xcode_settings.GetLdflags(
configuration_name, "PRODUCT_DIR", self.GypToBuildPath, "arm64"
)
# Do not quote `-arch arm64` with spaces in one string.
self.assertEqual(ldflags, ["-arch", "arm64", "-LPRODUCT_DIR"])
if __name__ == "__main__":
unittest.main()

View file

@ -13,15 +13,16 @@ of targets within Xcode.
"""
import errno
import gyp.generator.ninja
import os
import re
import xml.sax.saxutils
import gyp.generator.ninja
def _WriteWorkspace(main_gyp, sources_gyp, params):
""" Create a workspace to wrap main and sources gyp paths. """
(build_file_root, build_file_ext) = os.path.splitext(main_gyp)
"""Create a workspace to wrap main and sources gyp paths."""
(build_file_root, _build_file_ext) = os.path.splitext(main_gyp)
workspace_path = build_file_root + ".xcworkspace"
options = params["options"]
if options.generator_output:
@ -56,7 +57,7 @@ def _WriteWorkspace(main_gyp, sources_gyp, params):
def _TargetFromSpec(old_spec, params):
""" Create fake target for xcode-ninja wrapper. """
"""Create fake target for xcode-ninja wrapper."""
# Determine ninja top level build dir (e.g. /path/to/out).
ninja_toplevel = None
jobs = 0
@ -69,12 +70,11 @@ def _TargetFromSpec(old_spec, params):
target_name = old_spec.get("target_name")
product_name = old_spec.get("product_name", target_name)
product_extension = old_spec.get("product_extension")
ninja_target = {}
ninja_target["target_name"] = target_name
ninja_target["product_name"] = product_name
if product_extension:
if product_extension := old_spec.get("product_extension"):
ninja_target["product_extension"] = product_extension
ninja_target["toolset"] = old_spec.get("toolset")
ninja_target["default_configuration"] = old_spec.get("default_configuration")
@ -102,9 +102,9 @@ def _TargetFromSpec(old_spec, params):
new_xcode_settings[key] = old_xcode_settings[key]
ninja_target["configurations"][config] = {}
ninja_target["configurations"][config][
"xcode_settings"
] = new_xcode_settings
ninja_target["configurations"][config]["xcode_settings"] = (
new_xcode_settings
)
ninja_target["mac_bundle"] = old_spec.get("mac_bundle", 0)
ninja_target["mac_xctest_bundle"] = old_spec.get("mac_xctest_bundle", 0)
@ -137,13 +137,13 @@ def _TargetFromSpec(old_spec, params):
def IsValidTargetForWrapper(target_extras, executable_target_pattern, spec):
"""Limit targets for Xcode wrapper.
Xcode sometimes performs poorly with too many targets, so only include
proper executable targets, with filters to customize.
Arguments:
target_extras: Regular expression to always add, matching any target.
executable_target_pattern: Regular expression limiting executable targets.
spec: Specifications for target.
"""
Xcode sometimes performs poorly with too many targets, so only include
proper executable targets, with filters to customize.
Arguments:
target_extras: Regular expression to always add, matching any target.
executable_target_pattern: Regular expression limiting executable targets.
spec: Specifications for target.
"""
target_name = spec.get("target_name")
# Always include targets matching target_extras.
if target_extras is not None and re.search(target_extras, target_name):
@ -154,7 +154,6 @@ def IsValidTargetForWrapper(target_extras, executable_target_pattern, spec):
spec.get("type", "") == "executable"
and spec.get("product_extension", "") != "bundle"
):
# If there is a filter and the target does not match, exclude the target.
if executable_target_pattern is not None:
if not re.search(executable_target_pattern, target_name):
@ -166,14 +165,14 @@ def IsValidTargetForWrapper(target_extras, executable_target_pattern, spec):
def CreateWrapper(target_list, target_dicts, data, params):
"""Initialize targets for the ninja wrapper.
This sets up the necessary variables in the targets to generate Xcode projects
that use ninja as an external builder.
Arguments:
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
data: Dict of flattened build files keyed on gyp path.
params: Dict of global options for gyp.
"""
This sets up the necessary variables in the targets to generate Xcode projects
that use ninja as an external builder.
Arguments:
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
data: Dict of flattened build files keyed on gyp path.
params: Dict of global options for gyp.
"""
orig_gyp = params["build_files"][0]
for gyp_name, gyp_dict in data.items():
if gyp_name == orig_gyp:

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,6 @@ Working around this: http://bugs.python.org/issue5752
TODO(bradnelson): Consider dropping this when we drop XP support.
"""
import xml.dom.minidom

View file

@ -0,0 +1,3 @@
This software is made available under the terms of *either* of the licenses
found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
under the terms of *both* these licenses.

View file

@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View file

@ -0,0 +1,23 @@
Copyright (c) Donald Stufft and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,15 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
__title__ = "packaging"
__summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging"
__version__ = "23.3.dev0"
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
__license__ = "BSD-2-Clause or Apache-2.0"
__copyright__ = "2014 %s" % __author__

View file

@ -0,0 +1,107 @@
"""
ELF file parser.
This provides a class ``ELFFile`` that parses an ELF executable in a similar
interface to ``ZipFile``. Only the read interface is implemented.
Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
"""
import enum
import os
import struct
from typing import IO, Optional, Tuple
class ELFInvalid(ValueError):
pass
class EIClass(enum.IntEnum):
C32 = 1
C64 = 2
class EIData(enum.IntEnum):
Lsb = 1
Msb = 2
class EMachine(enum.IntEnum):
I386 = 3
S390 = 22
Arm = 40
X8664 = 62
AArc64 = 183
class ELFFile:
"""
Representation of an ELF executable.
"""
def __init__(self, f: IO[bytes]) -> None:
self._f = f
try:
ident = self._read("16B")
except struct.error:
raise ELFInvalid("unable to parse identification")
if (magic := bytes(ident[:4])) != b"\x7fELF":
raise ELFInvalid(f"invalid magic: {magic!r}")
self.capacity = ident[4] # Format for program header (bitness).
self.encoding = ident[5] # Data structure encoding (endianness).
try:
# e_fmt: Format for program header.
# p_fmt: Format for section header.
# p_idx: Indexes to find p_type, p_offset, and p_filesz.
e_fmt, self._p_fmt, self._p_idx = {
(1, 1): ("<HHIIIIIHHH", "<IIIIIIII", (0, 1, 4)), # 32-bit LSB.
(1, 2): (">HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB.
(2, 1): ("<HHIQQQIHHH", "<IIQQQQQQ", (0, 2, 5)), # 64-bit LSB.
(2, 2): (">HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB.
}[(self.capacity, self.encoding)]
except KeyError:
raise ELFInvalid(
f"unrecognized capacity ({self.capacity}) or "
f"encoding ({self.encoding})"
)
try:
(
_,
self.machine, # Architecture type.
_,
_,
self._e_phoff, # Offset of program header.
_,
self.flags, # Processor-specific flags.
_,
self._e_phentsize, # Size of section.
self._e_phnum, # Number of sections.
) = self._read(e_fmt)
except struct.error as e:
raise ELFInvalid("unable to parse machine and section information") from e
def _read(self, fmt: str) -> Tuple[int, ...]:
return struct.unpack(fmt, self._f.read(struct.calcsize(fmt)))
@property
def interpreter(self) -> Optional[str]:
"""
The path recorded in the ``PT_INTERP`` section header.
"""
for index in range(self._e_phnum):
self._f.seek(self._e_phoff + self._e_phentsize * index)
try:
data = self._read(self._p_fmt)
except struct.error:
continue
if data[self._p_idx[0]] != 3: # Not PT_INTERP.
continue
self._f.seek(data[self._p_idx[1]])
return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0")
return None

View file

@ -0,0 +1,252 @@
import collections
import contextlib
import functools
import os
import re
import sys
import warnings
from typing import Dict, Generator, Iterator, NamedTuple, Optional, Sequence, Tuple
from ._elffile import EIClass, EIData, ELFFile, EMachine
EF_ARM_ABIMASK = 0xFF000000
EF_ARM_ABI_VER5 = 0x05000000
EF_ARM_ABI_FLOAT_HARD = 0x00000400
# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
# as the type for `path` until then.
@contextlib.contextmanager
def _parse_elf(path: str) -> Generator[Optional[ELFFile], None, None]:
try:
with open(path, "rb") as f:
yield ELFFile(f)
except (OSError, TypeError, ValueError):
yield None
def _is_linux_armhf(executable: str) -> bool:
# hard-float ABI can be detected from the ELF header of the running
# process
# https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
with _parse_elf(executable) as f:
return (
f is not None
and f.capacity == EIClass.C32
and f.encoding == EIData.Lsb
and f.machine == EMachine.Arm
and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
)
def _is_linux_i686(executable: str) -> bool:
with _parse_elf(executable) as f:
return (
f is not None
and f.capacity == EIClass.C32
and f.encoding == EIData.Lsb
and f.machine == EMachine.I386
)
def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
if "armv7l" in archs:
return _is_linux_armhf(executable)
if "i686" in archs:
return _is_linux_i686(executable)
allowed_archs = {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x", "loongarch64"}
return any(arch in allowed_archs for arch in archs)
# If glibc ever changes its major version, we need to know what the last
# minor version was, so we can build the complete list of all versions.
# For now, guess what the highest minor version might be, assume it will
# be 50 for testing. Once this actually happens, update the dictionary
# with the actual value.
_LAST_GLIBC_MINOR: Dict[int, int] = collections.defaultdict(lambda: 50)
class _GLibCVersion(NamedTuple):
major: int
minor: int
def _glibc_version_string_confstr() -> Optional[str]:
"""
Primary implementation of glibc_version_string using os.confstr.
"""
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
# to be broken or missing. This strategy is used in the standard library
# platform module.
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
try:
# Should be a string like "glibc 2.17".
version_string: str = getattr(os, "confstr")("CS_GNU_LIBC_VERSION")
assert version_string is not None
_, version = version_string.rsplit()
except (AssertionError, AttributeError, OSError, ValueError):
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
return None
return version
def _glibc_version_string_ctypes() -> Optional[str]:
"""
Fallback implementation of glibc_version_string using ctypes.
"""
try:
import ctypes
except ImportError:
return None
# ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
# manpage says, "If filename is NULL, then the returned handle is for the
# main program". This way we can let the linker do the work to figure out
# which libc our process is actually using.
#
# We must also handle the special case where the executable is not a
# dynamically linked executable. This can occur when using musl libc,
# for example. In this situation, dlopen() will error, leading to an
# OSError. Interestingly, at least in the case of musl, there is no
# errno set on the OSError. The single string argument used to construct
# OSError comes from libc itself and is therefore not portable to
# hard code here. In any case, failure to call dlopen() means we
# can proceed, so we bail on our attempt.
try:
process_namespace = ctypes.CDLL(None)
except OSError:
return None
try:
gnu_get_libc_version = process_namespace.gnu_get_libc_version
except AttributeError:
# Symbol doesn't exist -> therefore, we are not linked to
# glibc.
return None
# Call gnu_get_libc_version, which returns a string like "2.5"
gnu_get_libc_version.restype = ctypes.c_char_p
version_str: str = gnu_get_libc_version()
# py2 / py3 compatibility:
if not isinstance(version_str, str):
version_str = version_str.decode("ascii")
return version_str
def _glibc_version_string() -> Optional[str]:
"""Returns glibc version string, or None if not using glibc."""
return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
def _parse_glibc_version(version_str: str) -> Tuple[int, int]:
"""Parse glibc version.
We use a regexp instead of str.split because we want to discard any
random junk that might come after the minor version -- this might happen
in patched/forked versions of glibc (e.g. Linaro's version of glibc
uses version strings like "2.20-2014.11"). See gh-3588.
"""
m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
if not m:
warnings.warn(
f"Expected glibc version with 2 components major.minor,"
f" got: {version_str}",
RuntimeWarning,
)
return -1, -1
return int(m.group("major")), int(m.group("minor"))
@functools.lru_cache()
def _get_glibc_version() -> Tuple[int, int]:
version_str = _glibc_version_string()
if version_str is None:
return (-1, -1)
return _parse_glibc_version(version_str)
# From PEP 513, PEP 600
def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
sys_glibc = _get_glibc_version()
if sys_glibc < version:
return False
# Check for presence of _manylinux module.
try:
import _manylinux # noqa
except ImportError:
return True
if hasattr(_manylinux, "manylinux_compatible"):
result = _manylinux.manylinux_compatible(version[0], version[1], arch)
if result is not None:
return bool(result)
return True
if version == _GLibCVersion(2, 5):
if hasattr(_manylinux, "manylinux1_compatible"):
return bool(_manylinux.manylinux1_compatible)
if version == _GLibCVersion(2, 12):
if hasattr(_manylinux, "manylinux2010_compatible"):
return bool(_manylinux.manylinux2010_compatible)
if version == _GLibCVersion(2, 17):
if hasattr(_manylinux, "manylinux2014_compatible"):
return bool(_manylinux.manylinux2014_compatible)
return True
_LEGACY_MANYLINUX_MAP = {
# CentOS 7 w/ glibc 2.17 (PEP 599)
(2, 17): "manylinux2014",
# CentOS 6 w/ glibc 2.12 (PEP 571)
(2, 12): "manylinux2010",
# CentOS 5 w/ glibc 2.5 (PEP 513)
(2, 5): "manylinux1",
}
def platform_tags(archs: Sequence[str]) -> Iterator[str]:
"""Generate manylinux tags compatible to the current platform.
:param archs: Sequence of compatible architectures.
The first one shall be the closest to the actual architecture and be the part of
platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
The ``linux_`` prefix is assumed as a prerequisite for the current platform to
be manylinux-compatible.
:returns: An iterator of compatible manylinux tags.
"""
if not _have_compatible_abi(sys.executable, archs):
return
# Oldest glibc to be supported regardless of architecture is (2, 17).
too_old_glibc2 = _GLibCVersion(2, 16)
if set(archs) & {"x86_64", "i686"}:
# On x86/i686 also oldest glibc to be supported is (2, 5).
too_old_glibc2 = _GLibCVersion(2, 4)
current_glibc = _GLibCVersion(*_get_glibc_version())
glibc_max_list = [current_glibc]
# We can assume compatibility across glibc major versions.
# https://sourceware.org/bugzilla/show_bug.cgi?id=24636
#
# Build a list of maximum glibc versions so that we can
# output the canonical list of all glibc from current_glibc
# down to too_old_glibc2, including all intermediary versions.
for glibc_major in range(current_glibc.major - 1, 1, -1):
glibc_minor = _LAST_GLIBC_MINOR[glibc_major]
glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor))
for arch in archs:
for glibc_max in glibc_max_list:
if glibc_max.major == too_old_glibc2.major:
min_minor = too_old_glibc2.minor
else:
# For other glibc major versions oldest supported is (x, 0).
min_minor = -1
for glibc_minor in range(glibc_max.minor, min_minor, -1):
glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
tag = "manylinux_{}_{}".format(*glibc_version)
if _is_compatible(arch, glibc_version):
yield f"{tag}_{arch}"
# Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
if glibc_version in _LEGACY_MANYLINUX_MAP:
legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
if _is_compatible(arch, glibc_version):
yield f"{legacy_tag}_{arch}"

View file

@ -0,0 +1,83 @@
"""PEP 656 support.
This module implements logic to detect if the currently running Python is
linked against musl, and what musl version is used.
"""
import functools
import re
import subprocess
import sys
from typing import Iterator, NamedTuple, Optional, Sequence
from ._elffile import ELFFile
class _MuslVersion(NamedTuple):
major: int
minor: int
def _parse_musl_version(output: str) -> Optional[_MuslVersion]:
lines = [n for n in (n.strip() for n in output.splitlines()) if n]
if len(lines) < 2 or lines[0][:4] != "musl":
return None
m = re.match(r"Version (\d+)\.(\d+)", lines[1])
if not m:
return None
return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
@functools.lru_cache()
def _get_musl_version(executable: str) -> Optional[_MuslVersion]:
"""Detect currently-running musl runtime version.
This is done by checking the specified executable's dynamic linking
information, and invoking the loader to parse its output for a version
string. If the loader is musl, the output would be something like::
musl libc (x86_64)
Version 1.2.2
Dynamic Program Loader
"""
try:
with open(executable, "rb") as f:
ld = ELFFile(f).interpreter
except (OSError, TypeError, ValueError):
return None
if ld is None or "musl" not in ld:
return None
proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True)
return _parse_musl_version(proc.stderr)
def platform_tags(archs: Sequence[str]) -> Iterator[str]:
"""Generate musllinux tags compatible to the current platform.
:param archs: Sequence of compatible architectures.
The first one shall be the closest to the actual architecture and be the part of
platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
The ``linux_`` prefix is assumed as a prerequisite for the current platform to
be musllinux-compatible.
:returns: An iterator of compatible musllinux tags.
"""
sys_musl = _get_musl_version(sys.executable)
if sys_musl is None: # Python not dynamically linked against musl.
return
for arch in archs:
for minor in range(sys_musl.minor, -1, -1):
yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
if __name__ == "__main__": # pragma: no cover
import sysconfig
plat = sysconfig.get_platform()
assert plat.startswith("linux-"), "not linux"
print("plat:", plat)
print("musl:", _get_musl_version(sys.executable))
print("tags:", end=" ")
for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):
print(t, end="\n ")

View file

@ -0,0 +1,359 @@
"""Handwritten parser of dependency specifiers.
The docstring for each __parse_* function contains ENBF-inspired grammar representing
the implementation.
"""
import ast
from typing import Any, List, NamedTuple, Optional, Tuple, Union
from ._tokenizer import DEFAULT_RULES, Tokenizer
class Node:
def __init__(self, value: str) -> None:
self.value = value
def __str__(self) -> str:
return self.value
def __repr__(self) -> str:
return f"<{self.__class__.__name__}('{self}')>"
def serialize(self) -> str:
raise NotImplementedError
class Variable(Node):
def serialize(self) -> str:
return str(self)
class Value(Node):
def serialize(self) -> str:
return f'"{self}"'
class Op(Node):
def serialize(self) -> str:
return str(self)
MarkerVar = Union[Variable, Value]
MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
# MarkerAtom = Union[MarkerItem, List["MarkerAtom"]]
# MarkerList = List[Union["MarkerList", MarkerAtom, str]]
# mypy does not support recursive type definition
# https://github.com/python/mypy/issues/731
MarkerAtom = Any
MarkerList = List[Any]
class ParsedRequirement(NamedTuple):
name: str
url: str
extras: List[str]
specifier: str
marker: Optional[MarkerList]
# --------------------------------------------------------------------------------------
# Recursive descent parser for dependency specifier
# --------------------------------------------------------------------------------------
def parse_requirement(source: str) -> ParsedRequirement:
return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES))
def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:
"""
requirement = WS? IDENTIFIER WS? extras WS? requirement_details
"""
tokenizer.consume("WS")
name_token = tokenizer.expect(
"IDENTIFIER", expected="package name at the start of dependency specifier"
)
name = name_token.text
tokenizer.consume("WS")
extras = _parse_extras(tokenizer)
tokenizer.consume("WS")
url, specifier, marker = _parse_requirement_details(tokenizer)
tokenizer.expect("END", expected="end of dependency specifier")
return ParsedRequirement(name, url, extras, specifier, marker)
def _parse_requirement_details(
tokenizer: Tokenizer,
) -> Tuple[str, str, Optional[MarkerList]]:
"""
requirement_details = AT URL (WS requirement_marker?)?
| specifier WS? (requirement_marker)?
"""
specifier = ""
url = ""
marker = None
if tokenizer.check("AT"):
tokenizer.read()
tokenizer.consume("WS")
url_start = tokenizer.position
url = tokenizer.expect("URL", expected="URL after @").text
if tokenizer.check("END", peek=True):
return (url, specifier, marker)
tokenizer.expect("WS", expected="whitespace after URL")
# The input might end after whitespace.
if tokenizer.check("END", peek=True):
return (url, specifier, marker)
marker = _parse_requirement_marker(
tokenizer, span_start=url_start, after="URL and whitespace"
)
else:
specifier_start = tokenizer.position
specifier = _parse_specifier(tokenizer)
tokenizer.consume("WS")
if tokenizer.check("END", peek=True):
return (url, specifier, marker)
marker = _parse_requirement_marker(
tokenizer,
span_start=specifier_start,
after=(
"version specifier"
if specifier
else "name and no valid version specifier"
),
)
return (url, specifier, marker)
def _parse_requirement_marker(
tokenizer: Tokenizer, *, span_start: int, after: str
) -> MarkerList:
"""
requirement_marker = SEMICOLON marker WS?
"""
if not tokenizer.check("SEMICOLON"):
tokenizer.raise_syntax_error(
f"Expected end or semicolon (after {after})",
span_start=span_start,
)
tokenizer.read()
marker = _parse_marker(tokenizer)
tokenizer.consume("WS")
return marker
def _parse_extras(tokenizer: Tokenizer) -> List[str]:
"""
extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)?
"""
if not tokenizer.check("LEFT_BRACKET", peek=True):
return []
with tokenizer.enclosing_tokens(
"LEFT_BRACKET",
"RIGHT_BRACKET",
around="extras",
):
tokenizer.consume("WS")
extras = _parse_extras_list(tokenizer)
tokenizer.consume("WS")
return extras
def _parse_extras_list(tokenizer: Tokenizer) -> List[str]:
"""
extras_list = identifier (wsp* ',' wsp* identifier)*
"""
extras: List[str] = []
if not tokenizer.check("IDENTIFIER"):
return extras
extras.append(tokenizer.read().text)
while True:
tokenizer.consume("WS")
if tokenizer.check("IDENTIFIER", peek=True):
tokenizer.raise_syntax_error("Expected comma between extra names")
elif not tokenizer.check("COMMA"):
break
tokenizer.read()
tokenizer.consume("WS")
extra_token = tokenizer.expect("IDENTIFIER", expected="extra name after comma")
extras.append(extra_token.text)
return extras
def _parse_specifier(tokenizer: Tokenizer) -> str:
"""
specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
| WS? version_many WS?
"""
with tokenizer.enclosing_tokens(
"LEFT_PARENTHESIS",
"RIGHT_PARENTHESIS",
around="version specifier",
):
tokenizer.consume("WS")
parsed_specifiers = _parse_version_many(tokenizer)
tokenizer.consume("WS")
return parsed_specifiers
def _parse_version_many(tokenizer: Tokenizer) -> str:
"""
version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)?
"""
parsed_specifiers = ""
while tokenizer.check("SPECIFIER"):
span_start = tokenizer.position
parsed_specifiers += tokenizer.read().text
if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True):
tokenizer.raise_syntax_error(
".* suffix can only be used with `==` or `!=` operators",
span_start=span_start,
span_end=tokenizer.position + 1,
)
if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True):
tokenizer.raise_syntax_error(
"Local version label can only be used with `==` or `!=` operators",
span_start=span_start,
span_end=tokenizer.position,
)
tokenizer.consume("WS")
if not tokenizer.check("COMMA"):
break
parsed_specifiers += tokenizer.read().text
tokenizer.consume("WS")
return parsed_specifiers
# --------------------------------------------------------------------------------------
# Recursive descent parser for marker expression
# --------------------------------------------------------------------------------------
def parse_marker(source: str) -> MarkerList:
return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES))
def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList:
retval = _parse_marker(tokenizer)
tokenizer.expect("END", expected="end of marker expression")
return retval
def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
"""
marker = marker_atom (BOOLOP marker_atom)+
"""
expression = [_parse_marker_atom(tokenizer)]
while tokenizer.check("BOOLOP"):
token = tokenizer.read()
expr_right = _parse_marker_atom(tokenizer)
expression.extend((token.text, expr_right))
return expression
def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:
"""
marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?
| WS? marker_item WS?
"""
tokenizer.consume("WS")
if tokenizer.check("LEFT_PARENTHESIS", peek=True):
with tokenizer.enclosing_tokens(
"LEFT_PARENTHESIS",
"RIGHT_PARENTHESIS",
around="marker expression",
):
tokenizer.consume("WS")
marker: MarkerAtom = _parse_marker(tokenizer)
tokenizer.consume("WS")
else:
marker = _parse_marker_item(tokenizer)
tokenizer.consume("WS")
return marker
def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem:
"""
marker_item = WS? marker_var WS? marker_op WS? marker_var WS?
"""
tokenizer.consume("WS")
marker_var_left = _parse_marker_var(tokenizer)
tokenizer.consume("WS")
marker_op = _parse_marker_op(tokenizer)
tokenizer.consume("WS")
marker_var_right = _parse_marker_var(tokenizer)
tokenizer.consume("WS")
return (marker_var_left, marker_op, marker_var_right)
def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar:
"""
marker_var = VARIABLE | QUOTED_STRING
"""
if tokenizer.check("VARIABLE"):
return process_env_var(tokenizer.read().text.replace(".", "_"))
elif tokenizer.check("QUOTED_STRING"):
return process_python_str(tokenizer.read().text)
else:
tokenizer.raise_syntax_error(
message="Expected a marker variable or quoted string"
)
def process_env_var(env_var: str) -> Variable:
if (
env_var == "platform_python_implementation"
or env_var == "python_implementation"
):
return Variable("platform_python_implementation")
else:
return Variable(env_var)
def process_python_str(python_str: str) -> Value:
value = ast.literal_eval(python_str)
return Value(str(value))
def _parse_marker_op(tokenizer: Tokenizer) -> Op:
"""
marker_op = IN | NOT IN | OP
"""
if tokenizer.check("IN"):
tokenizer.read()
return Op("in")
elif tokenizer.check("NOT"):
tokenizer.read()
tokenizer.expect("WS", expected="whitespace after 'not'")
tokenizer.expect("IN", expected="'in' after 'not'")
return Op("not in")
elif tokenizer.check("OP"):
return Op(tokenizer.read().text)
else:
return tokenizer.raise_syntax_error(
"Expected marker operator, one of "
"<=, <, !=, ==, >=, >, ~=, ===, in, not in"
)

View file

@ -0,0 +1,61 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
class InfinityType:
def __repr__(self) -> str:
return "Infinity"
def __hash__(self) -> int:
return hash(repr(self))
def __lt__(self, other: object) -> bool:
return False
def __le__(self, other: object) -> bool:
return False
def __eq__(self, other: object) -> bool:
return isinstance(other, self.__class__)
def __gt__(self, other: object) -> bool:
return True
def __ge__(self, other: object) -> bool:
return True
def __neg__(self: object) -> "NegativeInfinityType":
return NegativeInfinity
Infinity = InfinityType()
class NegativeInfinityType:
def __repr__(self) -> str:
return "-Infinity"
def __hash__(self) -> int:
return hash(repr(self))
def __lt__(self, other: object) -> bool:
return True
def __le__(self, other: object) -> bool:
return True
def __eq__(self, other: object) -> bool:
return isinstance(other, self.__class__)
def __gt__(self, other: object) -> bool:
return False
def __ge__(self, other: object) -> bool:
return False
def __neg__(self: object) -> InfinityType:
return Infinity
NegativeInfinity = NegativeInfinityType()

View file

@ -0,0 +1,192 @@
import contextlib
import re
from dataclasses import dataclass
from typing import Dict, Iterator, NoReturn, Optional, Tuple, Union
from .specifiers import Specifier
@dataclass
class Token:
name: str
text: str
position: int
class ParserSyntaxError(Exception):
"""The provided source text could not be parsed correctly."""
def __init__(
self,
message: str,
*,
source: str,
span: Tuple[int, int],
) -> None:
self.span = span
self.message = message
self.source = source
super().__init__()
def __str__(self) -> str:
marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^"
return "\n ".join([self.message, self.source, marker])
DEFAULT_RULES: "Dict[str, Union[str, re.Pattern[str]]]" = {
"LEFT_PARENTHESIS": r"\(",
"RIGHT_PARENTHESIS": r"\)",
"LEFT_BRACKET": r"\[",
"RIGHT_BRACKET": r"\]",
"SEMICOLON": r";",
"COMMA": r",",
"QUOTED_STRING": re.compile(
r"""
(
('[^']*')
|
("[^"]*")
)
""",
re.VERBOSE,
),
"OP": r"(===|==|~=|!=|<=|>=|<|>)",
"BOOLOP": r"\b(or|and)\b",
"IN": r"\bin\b",
"NOT": r"\bnot\b",
"VARIABLE": re.compile(
r"""
\b(
python_version
|python_full_version
|os[._]name
|sys[._]platform
|platform_(release|system)
|platform[._](version|machine|python_implementation)
|python_implementation
|implementation_(name|version)
|extra
)\b
""",
re.VERBOSE,
),
"SPECIFIER": re.compile(
Specifier._operator_regex_str + Specifier._version_regex_str,
re.VERBOSE | re.IGNORECASE,
),
"AT": r"\@",
"URL": r"[^ \t]+",
"IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b",
"VERSION_PREFIX_TRAIL": r"\.\*",
"VERSION_LOCAL_LABEL_TRAIL": r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*",
"WS": r"[ \t]+",
"END": r"$",
}
class Tokenizer:
"""Context-sensitive token parsing.
Provides methods to examine the input stream to check whether the next token
matches.
"""
def __init__(
self,
source: str,
*,
rules: "Dict[str, Union[str, re.Pattern[str]]]",
) -> None:
self.source = source
self.rules: Dict[str, re.Pattern[str]] = {
name: re.compile(pattern) for name, pattern in rules.items()
}
self.next_token: Optional[Token] = None
self.position = 0
def consume(self, name: str) -> None:
"""Move beyond provided token name, if at current position."""
if self.check(name):
self.read()
def check(self, name: str, *, peek: bool = False) -> bool:
"""Check whether the next token has the provided name.
By default, if the check succeeds, the token *must* be read before
another check. If `peek` is set to `True`, the token is not loaded and
would need to be checked again.
"""
assert (
self.next_token is None
), f"Cannot check for {name!r}, already have {self.next_token!r}"
assert name in self.rules, f"Unknown token name: {name!r}"
expression = self.rules[name]
match = expression.match(self.source, self.position)
if match is None:
return False
if not peek:
self.next_token = Token(name, match[0], self.position)
return True
def expect(self, name: str, *, expected: str) -> Token:
"""Expect a certain token name next, failing with a syntax error otherwise.
The token is *not* read.
"""
if not self.check(name):
raise self.raise_syntax_error(f"Expected {expected}")
return self.read()
def read(self) -> Token:
"""Consume the next token and return it."""
token = self.next_token
assert token is not None
self.position += len(token.text)
self.next_token = None
return token
def raise_syntax_error(
self,
message: str,
*,
span_start: Optional[int] = None,
span_end: Optional[int] = None,
) -> NoReturn:
"""Raise ParserSyntaxError at the given position."""
span = (
self.position if span_start is None else span_start,
self.position if span_end is None else span_end,
)
raise ParserSyntaxError(
message,
source=self.source,
span=span,
)
@contextlib.contextmanager
def enclosing_tokens(
self, open_token: str, close_token: str, *, around: str
) -> Iterator[None]:
if self.check(open_token):
open_position = self.position
self.read()
else:
open_position = None
yield
if open_position is None:
return
if not self.check(close_token):
self.raise_syntax_error(
f"Expected matching {close_token} for {open_token}, after {around}",
span_start=open_position,
)
self.read()

View file

@ -0,0 +1,251 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
import operator
import os
import platform
import sys
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
from ._parser import (
MarkerAtom,
MarkerList,
Op,
Value,
Variable,
parse_marker as _parse_marker,
)
from ._tokenizer import ParserSyntaxError
from .specifiers import InvalidSpecifier, Specifier
from .utils import canonicalize_name
__all__ = [
"InvalidMarker",
"UndefinedComparison",
"UndefinedEnvironmentName",
"Marker",
"default_environment",
]
Operator = Callable[[str, str], bool]
class InvalidMarker(ValueError):
"""
An invalid marker was found, users should refer to PEP 508.
"""
class UndefinedComparison(ValueError):
"""
An invalid operation was attempted on a value that doesn't support it.
"""
class UndefinedEnvironmentName(ValueError):
"""
A name was attempted to be used that does not exist inside of the
environment.
"""
def _normalize_extra_values(results: Any) -> Any:
"""
Normalize extra values.
"""
if isinstance(results[0], tuple):
lhs, op, rhs = results[0]
if isinstance(lhs, Variable) and lhs.value == "extra":
normalized_extra = canonicalize_name(rhs.value)
rhs = Value(normalized_extra)
elif isinstance(rhs, Variable) and rhs.value == "extra":
normalized_extra = canonicalize_name(lhs.value)
lhs = Value(normalized_extra)
results[0] = lhs, op, rhs
return results
def _format_marker(
marker: Union[List[str], MarkerAtom, str], first: Optional[bool] = True
) -> str:
assert isinstance(marker, (list, tuple, str))
# Sometimes we have a structure like [[...]] which is a single item list
# where the single item is itself it's own list. In that case we want skip
# the rest of this function so that we don't get extraneous () on the
# outside.
if (
isinstance(marker, list)
and len(marker) == 1
and isinstance(marker[0], (list, tuple))
):
return _format_marker(marker[0])
if isinstance(marker, list):
inner = (_format_marker(m, first=False) for m in marker)
if first:
return " ".join(inner)
else:
return "(" + " ".join(inner) + ")"
elif isinstance(marker, tuple):
return " ".join([m.serialize() for m in marker])
else:
return marker
_operators: Dict[str, Operator] = {
"in": lambda lhs, rhs: lhs in rhs,
"not in": lambda lhs, rhs: lhs not in rhs,
"<": operator.lt,
"<=": operator.le,
"==": operator.eq,
"!=": operator.ne,
">=": operator.ge,
">": operator.gt,
}
def _eval_op(lhs: str, op: Op, rhs: str) -> bool:
try:
spec = Specifier("".join([op.serialize(), rhs]))
except InvalidSpecifier:
pass
else:
return spec.contains(lhs, prereleases=True)
oper: Optional[Operator] = _operators.get(op.serialize())
if oper is None:
raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")
return oper(lhs, rhs)
def _normalize(*values: str, key: str) -> Tuple[str, ...]:
# PEP 685 Comparison of extra names for optional distribution dependencies
# https://peps.python.org/pep-0685/
# > When comparing extra names, tools MUST normalize the names being
# > compared using the semantics outlined in PEP 503 for names
if key == "extra":
return tuple(canonicalize_name(v) for v in values)
# other environment markers don't have such standards
return values
def _evaluate_markers(markers: MarkerList, environment: Dict[str, str]) -> bool:
groups: List[List[bool]] = [[]]
for marker in markers:
assert isinstance(marker, (list, tuple, str))
if isinstance(marker, list):
groups[-1].append(_evaluate_markers(marker, environment))
elif isinstance(marker, tuple):
lhs, op, rhs = marker
if isinstance(lhs, Variable):
environment_key = lhs.value
lhs_value = environment[environment_key]
rhs_value = rhs.value
else:
lhs_value = lhs.value
environment_key = rhs.value
rhs_value = environment[environment_key]
lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
groups[-1].append(_eval_op(lhs_value, op, rhs_value))
else:
assert marker in ["and", "or"]
if marker == "or":
groups.append([])
return any(all(item) for item in groups)
def format_full_version(info: "sys._version_info") -> str:
version = "{0.major}.{0.minor}.{0.micro}".format(info)
if (kind := info.releaselevel) != "final":
version += kind[0] + str(info.serial)
return version
def default_environment() -> Dict[str, str]:
iver = format_full_version(sys.implementation.version)
implementation_name = sys.implementation.name
return {
"implementation_name": implementation_name,
"implementation_version": iver,
"os_name": os.name,
"platform_machine": platform.machine(),
"platform_release": platform.release(),
"platform_system": platform.system(),
"platform_version": platform.version(),
"python_full_version": platform.python_version(),
"platform_python_implementation": platform.python_implementation(),
"python_version": ".".join(platform.python_version_tuple()[:2]),
"sys_platform": sys.platform,
}
class Marker:
def __init__(self, marker: str) -> None:
# Note: We create a Marker object without calling this constructor in
# packaging.requirements.Requirement. If any additional logic is
# added here, make sure to mirror/adapt Requirement.
try:
self._markers = _normalize_extra_values(_parse_marker(marker))
# The attribute `_markers` can be described in terms of a recursive type:
# MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]]
#
# For example, the following expression:
# python_version > "3.6" or (python_version == "3.6" and os_name == "unix")
#
# is parsed into:
# [
# (<Variable('python_version')>, <Op('>')>, <Value('3.6')>),
# 'and',
# [
# (<Variable('python_version')>, <Op('==')>, <Value('3.6')>),
# 'or',
# (<Variable('os_name')>, <Op('==')>, <Value('unix')>)
# ]
# ]
except ParserSyntaxError as e:
raise InvalidMarker(str(e)) from e
def __str__(self) -> str:
return _format_marker(self._markers)
def __repr__(self) -> str:
return f"<Marker('{self}')>"
def __hash__(self) -> int:
return hash((self.__class__.__name__, str(self)))
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Marker):
return NotImplemented
return str(self) == str(other)
def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool:
"""Evaluate a marker.
Return the boolean from evaluating the given marker against the
environment. environment is an optional argument to override all or
part of the determined environment.
The environment is determined from the current Python process.
"""
current_environment = default_environment()
current_environment["extra"] = ""
if environment is not None:
current_environment.update(environment)
# The API used to allow setting extra to None. We need to handle this
# case for backwards compatibility.
if current_environment["extra"] is None:
current_environment["extra"] = ""
return _evaluate_markers(self._markers, current_environment)

View file

@ -0,0 +1,807 @@
import email.feedparser
import email.header
import email.message
import email.parser
import email.policy
import sys
import typing
from typing import (
Any,
Callable,
Dict,
Generic,
List,
Optional,
Tuple,
Type,
Union,
cast,
)
from . import requirements, specifiers, utils, version as version_module
T = typing.TypeVar("T")
from typing import Literal, TypedDict
try:
ExceptionGroup # Added in Python 3.11+
except NameError: # pragma: no cover
class ExceptionGroup(Exception): # noqa: N818
"""A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11.
If :external:exc:`ExceptionGroup` is already defined by Python itself,
that version is used instead.
"""
message: str
exceptions: List[Exception]
def __init__(self, message: str, exceptions: List[Exception]) -> None:
self.message = message
self.exceptions = exceptions
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})"
else: # pragma: no cover
ExceptionGroup = ExceptionGroup
class InvalidMetadata(ValueError):
"""A metadata field contains invalid data."""
field: str
"""The name of the field that contains invalid data."""
def __init__(self, field: str, message: str) -> None:
self.field = field
super().__init__(message)
# The RawMetadata class attempts to make as few assumptions about the underlying
# serialization formats as possible. The idea is that as long as a serialization
# formats offer some very basic primitives in *some* way then we can support
# serializing to and from that format.
class RawMetadata(TypedDict, total=False):
"""A dictionary of raw core metadata.
Each field in core metadata maps to a key of this dictionary (when data is
provided). The key is lower-case and underscores are used instead of dashes
compared to the equivalent core metadata field. Any core metadata field that
can be specified multiple times or can hold multiple values in a single
field have a key with a plural name. See :class:`Metadata` whose attributes
match the keys of this dictionary.
Core metadata fields that can be specified multiple times are stored as a
list or dict depending on which is appropriate for the field. Any fields
which hold multiple values in a single field are stored as a list.
"""
# Metadata 1.0 - PEP 241
metadata_version: str
name: str
version: str
platforms: List[str]
summary: str
description: str
keywords: List[str]
home_page: str
author: str
author_email: str
license: str
# Metadata 1.1 - PEP 314
supported_platforms: List[str]
download_url: str
classifiers: List[str]
requires: List[str]
provides: List[str]
obsoletes: List[str]
# Metadata 1.2 - PEP 345
maintainer: str
maintainer_email: str
requires_dist: List[str]
provides_dist: List[str]
obsoletes_dist: List[str]
requires_python: str
requires_external: List[str]
project_urls: Dict[str, str]
# Metadata 2.0
# PEP 426 attempted to completely revamp the metadata format
# but got stuck without ever being able to build consensus on
# it and ultimately ended up withdrawn.
#
# However, a number of tools had started emitting METADATA with
# `2.0` Metadata-Version, so for historical reasons, this version
# was skipped.
# Metadata 2.1 - PEP 566
description_content_type: str
provides_extra: List[str]
# Metadata 2.2 - PEP 643
dynamic: List[str]
# Metadata 2.3 - PEP 685
# No new fields were added in PEP 685, just some edge case were
# tightened up to provide better interoperability.
_STRING_FIELDS = {
"author",
"author_email",
"description",
"description_content_type",
"download_url",
"home_page",
"license",
"maintainer",
"maintainer_email",
"metadata_version",
"name",
"requires_python",
"summary",
"version",
}
_LIST_FIELDS = {
"classifiers",
"dynamic",
"obsoletes",
"obsoletes_dist",
"platforms",
"provides",
"provides_dist",
"provides_extra",
"requires",
"requires_dist",
"requires_external",
"supported_platforms",
}
_DICT_FIELDS = {
"project_urls",
}
def _parse_keywords(data: str) -> List[str]:
"""Split a string of comma-separate keyboards into a list of keywords."""
return [k.strip() for k in data.split(",")]
def _parse_project_urls(data: List[str]) -> Dict[str, str]:
"""Parse a list of label/URL string pairings separated by a comma."""
urls = {}
for pair in data:
# Our logic is slightly tricky here as we want to try and do
# *something* reasonable with malformed data.
#
# The main thing that we have to worry about, is data that does
# not have a ',' at all to split the label from the Value. There
# isn't a singular right answer here, and we will fail validation
# later on (if the caller is validating) so it doesn't *really*
# matter, but since the missing value has to be an empty str
# and our return value is dict[str, str], if we let the key
# be the missing value, then they'd have multiple '' values that
# overwrite each other in a accumulating dict.
#
# The other potential issue is that it's possible to have the
# same label multiple times in the metadata, with no solid "right"
# answer with what to do in that case. As such, we'll do the only
# thing we can, which is treat the field as unparsable and add it
# to our list of unparsed fields.
parts = [p.strip() for p in pair.split(",", 1)]
parts.extend([""] * (max(0, 2 - len(parts)))) # Ensure 2 items
# TODO: The spec doesn't say anything about if the keys should be
# considered case sensitive or not... logically they should
# be case-preserving and case-insensitive, but doing that
# would open up more cases where we might have duplicate
# entries.
label, url = parts
if label in urls:
# The label already exists in our set of urls, so this field
# is unparsable, and we can just add the whole thing to our
# unparsable data and stop processing it.
raise KeyError("duplicate labels in project urls")
urls[label] = url
return urls
def _get_payload(msg: email.message.Message, source: Union[bytes, str]) -> str:
"""Get the body of the message."""
# If our source is a str, then our caller has managed encodings for us,
# and we don't need to deal with it.
if isinstance(source, str):
payload: str = msg.get_payload()
return payload
# If our source is a bytes, then we're managing the encoding and we need
# to deal with it.
else:
bpayload: bytes = msg.get_payload(decode=True)
try:
return bpayload.decode("utf8", "strict")
except UnicodeDecodeError:
raise ValueError("payload in an invalid encoding")
# The various parse_FORMAT functions here are intended to be as lenient as
# possible in their parsing, while still returning a correctly typed
# RawMetadata.
#
# To aid in this, we also generally want to do as little touching of the
# data as possible, except where there are possibly some historic holdovers
# that make valid data awkward to work with.
#
# While this is a lower level, intermediate format than our ``Metadata``
# class, some light touch ups can make a massive difference in usability.
# Map METADATA fields to RawMetadata.
_EMAIL_TO_RAW_MAPPING = {
"author": "author",
"author-email": "author_email",
"classifier": "classifiers",
"description": "description",
"description-content-type": "description_content_type",
"download-url": "download_url",
"dynamic": "dynamic",
"home-page": "home_page",
"keywords": "keywords",
"license": "license",
"maintainer": "maintainer",
"maintainer-email": "maintainer_email",
"metadata-version": "metadata_version",
"name": "name",
"obsoletes": "obsoletes",
"obsoletes-dist": "obsoletes_dist",
"platform": "platforms",
"project-url": "project_urls",
"provides": "provides",
"provides-dist": "provides_dist",
"provides-extra": "provides_extra",
"requires": "requires",
"requires-dist": "requires_dist",
"requires-external": "requires_external",
"requires-python": "requires_python",
"summary": "summary",
"supported-platform": "supported_platforms",
"version": "version",
}
_RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()}
def parse_email(data: Union[bytes, str]) -> Tuple[RawMetadata, Dict[str, List[str]]]:
"""Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``).
This function returns a two-item tuple of dicts. The first dict is of
recognized fields from the core metadata specification. Fields that can be
parsed and translated into Python's built-in types are converted
appropriately. All other fields are left as-is. Fields that are allowed to
appear multiple times are stored as lists.
The second dict contains all other fields from the metadata. This includes
any unrecognized fields. It also includes any fields which are expected to
be parsed into a built-in type but were not formatted appropriately. Finally,
any fields that are expected to appear only once but are repeated are
included in this dict.
"""
raw: Dict[str, Union[str, List[str], Dict[str, str]]] = {}
unparsed: Dict[str, List[str]] = {}
if isinstance(data, str):
parsed = email.parser.Parser(policy=email.policy.compat32).parsestr(data)
else:
parsed = email.parser.BytesParser(policy=email.policy.compat32).parsebytes(data)
# We have to wrap parsed.keys() in a set, because in the case of multiple
# values for a key (a list), the key will appear multiple times in the
# list of keys, but we're avoiding that by using get_all().
for name in frozenset(parsed.keys()):
# Header names in RFC are case insensitive, so we'll normalize to all
# lower case to make comparisons easier.
name = name.lower()
# We use get_all() here, even for fields that aren't multiple use,
# because otherwise someone could have e.g. two Name fields, and we
# would just silently ignore it rather than doing something about it.
headers = parsed.get_all(name) or []
# The way the email module works when parsing bytes is that it
# unconditionally decodes the bytes as ascii using the surrogateescape
# handler. When you pull that data back out (such as with get_all() ),
# it looks to see if the str has any surrogate escapes, and if it does
# it wraps it in a Header object instead of returning the string.
#
# As such, we'll look for those Header objects, and fix up the encoding.
value = []
# Flag if we have run into any issues processing the headers, thus
# signalling that the data belongs in 'unparsed'.
valid_encoding = True
for h in headers:
# It's unclear if this can return more types than just a Header or
# a str, so we'll just assert here to make sure.
assert isinstance(h, (email.header.Header, str))
# If it's a header object, we need to do our little dance to get
# the real data out of it. In cases where there is invalid data
# we're going to end up with mojibake, but there's no obvious, good
# way around that without reimplementing parts of the Header object
# ourselves.
#
# That should be fine since, if mojibacked happens, this key is
# going into the unparsed dict anyways.
if isinstance(h, email.header.Header):
# The Header object stores it's data as chunks, and each chunk
# can be independently encoded, so we'll need to check each
# of them.
chunks: List[Tuple[bytes, Optional[str]]] = []
for bin, encoding in email.header.decode_header(h):
try:
bin.decode("utf8", "strict")
except UnicodeDecodeError:
# Enable mojibake.
encoding = "latin1"
valid_encoding = False
else:
encoding = "utf8"
chunks.append((bin, encoding))
# Turn our chunks back into a Header object, then let that
# Header object do the right thing to turn them into a
# string for us.
value.append(str(email.header.make_header(chunks)))
# This is already a string, so just add it.
else:
value.append(h)
# We've processed all of our values to get them into a list of str,
# but we may have mojibake data, in which case this is an unparsed
# field.
if not valid_encoding:
unparsed[name] = value
continue
raw_name = _EMAIL_TO_RAW_MAPPING.get(name)
if raw_name is None:
# This is a bit of a weird situation, we've encountered a key that
# we don't know what it means, so we don't know whether it's meant
# to be a list or not.
#
# Since we can't really tell one way or another, we'll just leave it
# as a list, even though it may be a single item list, because that's
# what makes the most sense for email headers.
unparsed[name] = value
continue
# If this is one of our string fields, then we'll check to see if our
# value is a list of a single item. If it is then we'll assume that
# it was emitted as a single string, and unwrap the str from inside
# the list.
#
# If it's any other kind of data, then we haven't the faintest clue
# what we should parse it as, and we have to just add it to our list
# of unparsed stuff.
if raw_name in _STRING_FIELDS and len(value) == 1:
raw[raw_name] = value[0]
# If this is one of our list of string fields, then we can just assign
# the value, since email *only* has strings, and our get_all() call
# above ensures that this is a list.
elif raw_name in _LIST_FIELDS:
raw[raw_name] = value
# Special Case: Keywords
# The keywords field is implemented in the metadata spec as a str,
# but it conceptually is a list of strings, and is serialized using
# ", ".join(keywords), so we'll do some light data massaging to turn
# this into what it logically is.
elif raw_name == "keywords" and len(value) == 1:
raw[raw_name] = _parse_keywords(value[0])
# Special Case: Project-URL
# The project urls is implemented in the metadata spec as a list of
# specially-formatted strings that represent a key and a value, which
# is fundamentally a mapping, however the email format doesn't support
# mappings in a sane way, so it was crammed into a list of strings
# instead.
#
# We will do a little light data massaging to turn this into a map as
# it logically should be.
elif raw_name == "project_urls":
try:
raw[raw_name] = _parse_project_urls(value)
except KeyError:
unparsed[name] = value
# Nothing that we've done has managed to parse this, so it'll just
# throw it in our unparsable data and move on.
else:
unparsed[name] = value
# We need to support getting the Description from the message payload in
# addition to getting it from the the headers. This does mean, though, there
# is the possibility of it being set both ways, in which case we put both
# in 'unparsed' since we don't know which is right.
try:
payload = _get_payload(parsed, data)
except ValueError:
unparsed.setdefault("description", []).append(
parsed.get_payload(decode=isinstance(data, bytes))
)
else:
if payload:
# Check to see if we've already got a description, if so then both
# it, and this body move to unparsable.
if "description" in raw:
description_header = cast(str, raw.pop("description"))
unparsed.setdefault("description", []).extend(
[description_header, payload]
)
elif "description" in unparsed:
unparsed["description"].append(payload)
else:
raw["description"] = payload
# We need to cast our `raw` to a metadata, because a TypedDict only support
# literal key names, but we're computing our key names on purpose, but the
# way this function is implemented, our `TypedDict` can only have valid key
# names.
return cast(RawMetadata, raw), unparsed
_NOT_FOUND = object()
# Keep the two values in sync.
_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3"]
_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3"]
_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"])
class _Validator(Generic[T]):
"""Validate a metadata field.
All _process_*() methods correspond to a core metadata field. The method is
called with the field's raw value. If the raw value is valid it is returned
in its "enriched" form (e.g. ``version.Version`` for the ``Version`` field).
If the raw value is invalid, :exc:`InvalidMetadata` is raised (with a cause
as appropriate).
"""
name: str
raw_name: str
added: _MetadataVersion
def __init__(
self,
*,
added: _MetadataVersion = "1.0",
) -> None:
self.added = added
def __set_name__(self, _owner: "Metadata", name: str) -> None:
self.name = name
self.raw_name = _RAW_TO_EMAIL_MAPPING[name]
def __get__(self, instance: "Metadata", _owner: Type["Metadata"]) -> T:
# With Python 3.8+, the caching can be replaced with functools.cached_property().
# No need to check the cache as attribute lookup will resolve into the
# instance's __dict__ before __get__ is called.
cache = instance.__dict__
value = instance._raw.get(self.name)
# To make the _process_* methods easier, we'll check if the value is None
# and if this field is NOT a required attribute, and if both of those
# things are true, we'll skip the the converter. This will mean that the
# converters never have to deal with the None union.
if self.name in _REQUIRED_ATTRS or value is not None:
try:
converter: Callable[[Any], T] = getattr(self, f"_process_{self.name}")
except AttributeError:
pass
else:
value = converter(value)
cache[self.name] = value
try:
del instance._raw[self.name] # type: ignore[misc]
except KeyError:
pass
return cast(T, value)
def _invalid_metadata(
self, msg: str, cause: Optional[Exception] = None
) -> InvalidMetadata:
exc = InvalidMetadata(
self.raw_name, msg.format_map({"field": repr(self.raw_name)})
)
exc.__cause__ = cause
return exc
def _process_metadata_version(self, value: str) -> _MetadataVersion:
# Implicitly makes Metadata-Version required.
if value not in _VALID_METADATA_VERSIONS:
raise self._invalid_metadata(f"{value!r} is not a valid metadata version")
return cast(_MetadataVersion, value)
def _process_name(self, value: str) -> str:
if not value:
raise self._invalid_metadata("{field} is a required field")
# Validate the name as a side-effect.
try:
utils.canonicalize_name(value, validate=True)
except utils.InvalidName as exc:
raise self._invalid_metadata(
f"{value!r} is invalid for {{field}}", cause=exc
)
else:
return value
def _process_version(self, value: str) -> version_module.Version:
if not value:
raise self._invalid_metadata("{field} is a required field")
try:
return version_module.parse(value)
except version_module.InvalidVersion as exc:
raise self._invalid_metadata(
f"{value!r} is invalid for {{field}}", cause=exc
)
def _process_summary(self, value: str) -> str:
"""Check the field contains no newlines."""
if "\n" in value:
raise self._invalid_metadata("{field} must be a single line")
return value
def _process_description_content_type(self, value: str) -> str:
content_types = {"text/plain", "text/x-rst", "text/markdown"}
message = email.message.EmailMessage()
message["content-type"] = value
content_type, parameters = (
# Defaults to `text/plain` if parsing failed.
message.get_content_type().lower(),
message["content-type"].params,
)
# Check if content-type is valid or defaulted to `text/plain` and thus was
# not parseable.
if content_type not in content_types or content_type not in value.lower():
raise self._invalid_metadata(
f"{{field}} must be one of {list(content_types)}, not {value!r}"
)
if (charset := parameters.get("charset", "UTF-8")) != "UTF-8":
raise self._invalid_metadata(
f"{{field}} can only specify the UTF-8 charset, not {list(charset)}"
)
markdown_variants = {"GFM", "CommonMark"}
variant = parameters.get("variant", "GFM") # Use an acceptable default.
if content_type == "text/markdown" and variant not in markdown_variants:
raise self._invalid_metadata(
f"valid Markdown variants for {{field}} are {list(markdown_variants)}, "
f"not {variant!r}",
)
return value
def _process_dynamic(self, value: List[str]) -> List[str]:
for dynamic_field in map(str.lower, value):
if dynamic_field in {"name", "version", "metadata-version"}:
raise self._invalid_metadata(
f"{value!r} is not allowed as a dynamic field"
)
elif dynamic_field not in _EMAIL_TO_RAW_MAPPING:
raise self._invalid_metadata(f"{value!r} is not a valid dynamic field")
return list(map(str.lower, value))
def _process_provides_extra(
self,
value: List[str],
) -> List[utils.NormalizedName]:
normalized_names = []
try:
for name in value:
normalized_names.append(utils.canonicalize_name(name, validate=True))
except utils.InvalidName as exc:
raise self._invalid_metadata(
f"{name!r} is invalid for {{field}}", cause=exc
)
else:
return normalized_names
def _process_requires_python(self, value: str) -> specifiers.SpecifierSet:
try:
return specifiers.SpecifierSet(value)
except specifiers.InvalidSpecifier as exc:
raise self._invalid_metadata(
f"{value!r} is invalid for {{field}}", cause=exc
)
def _process_requires_dist(
self,
value: List[str],
) -> List[requirements.Requirement]:
reqs = []
try:
for req in value:
reqs.append(requirements.Requirement(req))
except requirements.InvalidRequirement as exc:
raise self._invalid_metadata(f"{req!r} is invalid for {{field}}", cause=exc)
else:
return reqs
class Metadata:
"""Representation of distribution metadata.
Compared to :class:`RawMetadata`, this class provides objects representing
metadata fields instead of only using built-in types. Any invalid metadata
will cause :exc:`InvalidMetadata` to be raised (with a
:py:attr:`~BaseException.__cause__` attribute as appropriate).
"""
_raw: RawMetadata
@classmethod
def from_raw(cls, data: RawMetadata, *, validate: bool = True) -> "Metadata":
"""Create an instance from :class:`RawMetadata`.
If *validate* is true, all metadata will be validated. All exceptions
related to validation will be gathered and raised as an :class:`ExceptionGroup`.
"""
ins = cls()
ins._raw = data.copy() # Mutations occur due to caching enriched values.
if validate:
exceptions: List[Exception] = []
try:
metadata_version = ins.metadata_version
metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version)
except InvalidMetadata as metadata_version_exc:
exceptions.append(metadata_version_exc)
metadata_version = None
# Make sure to check for the fields that are present, the required
# fields (so their absence can be reported).
fields_to_check = frozenset(ins._raw) | _REQUIRED_ATTRS
# Remove fields that have already been checked.
fields_to_check -= {"metadata_version"}
for key in fields_to_check:
try:
if metadata_version:
# Can't use getattr() as that triggers descriptor protocol which
# will fail due to no value for the instance argument.
try:
field_metadata_version = cls.__dict__[key].added
except KeyError:
exc = InvalidMetadata(key, f"unrecognized field: {key!r}")
exceptions.append(exc)
continue
field_age = _VALID_METADATA_VERSIONS.index(
field_metadata_version
)
if field_age > metadata_age:
field = _RAW_TO_EMAIL_MAPPING[key]
exc = InvalidMetadata(
field,
"{field} introduced in metadata version "
"{field_metadata_version}, not {metadata_version}",
)
exceptions.append(exc)
continue
getattr(ins, key)
except InvalidMetadata as exc:
exceptions.append(exc)
if exceptions:
raise ExceptionGroup("invalid metadata", exceptions)
return ins
@classmethod
def from_email(
cls, data: Union[bytes, str], *, validate: bool = True
) -> "Metadata":
"""Parse metadata from email headers.
If *validate* is true, the metadata will be validated. All exceptions
related to validation will be gathered and raised as an :class:`ExceptionGroup`.
"""
raw, unparsed = parse_email(data)
if validate:
exceptions: list[Exception] = []
for unparsed_key in unparsed:
if unparsed_key in _EMAIL_TO_RAW_MAPPING:
message = f"{unparsed_key!r} has invalid data"
else:
message = f"unrecognized field: {unparsed_key!r}"
exceptions.append(InvalidMetadata(unparsed_key, message))
if exceptions:
raise ExceptionGroup("unparsed", exceptions)
try:
return cls.from_raw(raw, validate=validate)
except ExceptionGroup as exc_group:
raise ExceptionGroup(
"invalid or unparsed metadata", exc_group.exceptions
) from None
metadata_version: _Validator[_MetadataVersion] = _Validator()
""":external:ref:`core-metadata-metadata-version`
(required; validated to be a valid metadata version)"""
name: _Validator[str] = _Validator()
""":external:ref:`core-metadata-name`
(required; validated using :func:`~packaging.utils.canonicalize_name` and its
*validate* parameter)"""
version: _Validator[version_module.Version] = _Validator()
""":external:ref:`core-metadata-version` (required)"""
dynamic: _Validator[Optional[List[str]]] = _Validator(
added="2.2",
)
""":external:ref:`core-metadata-dynamic`
(validated against core metadata field names and lowercased)"""
platforms: _Validator[Optional[List[str]]] = _Validator()
""":external:ref:`core-metadata-platform`"""
supported_platforms: _Validator[Optional[List[str]]] = _Validator(added="1.1")
""":external:ref:`core-metadata-supported-platform`"""
summary: _Validator[Optional[str]] = _Validator()
""":external:ref:`core-metadata-summary` (validated to contain no newlines)"""
description: _Validator[Optional[str]] = _Validator() # TODO 2.1: can be in body
""":external:ref:`core-metadata-description`"""
description_content_type: _Validator[Optional[str]] = _Validator(added="2.1")
""":external:ref:`core-metadata-description-content-type` (validated)"""
keywords: _Validator[Optional[List[str]]] = _Validator()
""":external:ref:`core-metadata-keywords`"""
home_page: _Validator[Optional[str]] = _Validator()
""":external:ref:`core-metadata-home-page`"""
download_url: _Validator[Optional[str]] = _Validator(added="1.1")
""":external:ref:`core-metadata-download-url`"""
author: _Validator[Optional[str]] = _Validator()
""":external:ref:`core-metadata-author`"""
author_email: _Validator[Optional[str]] = _Validator()
""":external:ref:`core-metadata-author-email`"""
maintainer: _Validator[Optional[str]] = _Validator(added="1.2")
""":external:ref:`core-metadata-maintainer`"""
maintainer_email: _Validator[Optional[str]] = _Validator(added="1.2")
""":external:ref:`core-metadata-maintainer-email`"""
license: _Validator[Optional[str]] = _Validator()
""":external:ref:`core-metadata-license`"""
classifiers: _Validator[Optional[List[str]]] = _Validator(added="1.1")
""":external:ref:`core-metadata-classifier`"""
requires_dist: _Validator[Optional[List[requirements.Requirement]]] = _Validator(
added="1.2"
)
""":external:ref:`core-metadata-requires-dist`"""
requires_python: _Validator[Optional[specifiers.SpecifierSet]] = _Validator(
added="1.2"
)
""":external:ref:`core-metadata-requires-python`"""
# Because `Requires-External` allows for non-PEP 440 version specifiers, we
# don't do any processing on the values.
requires_external: _Validator[Optional[List[str]]] = _Validator(added="1.2")
""":external:ref:`core-metadata-requires-external`"""
project_urls: _Validator[Optional[Dict[str, str]]] = _Validator(added="1.2")
""":external:ref:`core-metadata-project-url`"""
# PEP 685 lets us raise an error if an extra doesn't pass `Name` validation
# regardless of metadata version.
provides_extra: _Validator[Optional[List[utils.NormalizedName]]] = _Validator(
added="2.1",
)
""":external:ref:`core-metadata-provides-extra`"""
provides_dist: _Validator[Optional[List[str]]] = _Validator(added="1.2")
""":external:ref:`core-metadata-provides-dist`"""
obsoletes_dist: _Validator[Optional[List[str]]] = _Validator(added="1.2")
""":external:ref:`core-metadata-obsoletes-dist`"""
requires: _Validator[Optional[List[str]]] = _Validator(added="1.1")
"""``Requires`` (deprecated)"""
provides: _Validator[Optional[List[str]]] = _Validator(added="1.1")
"""``Provides`` (deprecated)"""
obsoletes: _Validator[Optional[List[str]]] = _Validator(added="1.1")
"""``Obsoletes`` (deprecated)"""

View file

View file

@ -0,0 +1,90 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from typing import Any, Iterator, Optional, Set
from ._parser import parse_requirement as _parse_requirement
from ._tokenizer import ParserSyntaxError
from .markers import Marker, _normalize_extra_values
from .specifiers import SpecifierSet
from .utils import canonicalize_name
class InvalidRequirement(ValueError):
"""
An invalid requirement was found, users should refer to PEP 508.
"""
class Requirement:
"""Parse a requirement.
Parse a given requirement string into its parts, such as name, specifier,
URL, and extras. Raises InvalidRequirement on a badly-formed requirement
string.
"""
# TODO: Can we test whether something is contained within a requirement?
# If so how do we do that? Do we need to test against the _name_ of
# the thing as well as the version? What about the markers?
# TODO: Can we normalize the name and extra name?
def __init__(self, requirement_string: str) -> None:
try:
parsed = _parse_requirement(requirement_string)
except ParserSyntaxError as e:
raise InvalidRequirement(str(e)) from e
self.name: str = parsed.name
self.url: Optional[str] = parsed.url or None
self.extras: Set[str] = set(parsed.extras if parsed.extras else [])
self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)
self.marker: Optional[Marker] = None
if parsed.marker is not None:
self.marker = Marker.__new__(Marker)
self.marker._markers = _normalize_extra_values(parsed.marker)
def _iter_parts(self, name: str) -> Iterator[str]:
yield name
if self.extras:
formatted_extras = ",".join(sorted(self.extras))
yield f"[{formatted_extras}]"
if self.specifier:
yield str(self.specifier)
if self.url:
yield f"@ {self.url}"
if self.marker:
yield " "
if self.marker:
yield f"; {self.marker}"
def __str__(self) -> str:
return "".join(self._iter_parts(self.name))
def __repr__(self) -> str:
return f"<Requirement('{self}')>"
def __hash__(self) -> int:
return hash(
(
self.__class__.__name__,
*self._iter_parts(canonicalize_name(self.name)),
)
)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Requirement):
return NotImplemented
return (
canonicalize_name(self.name) == canonicalize_name(other.name)
and self.extras == other.extras
and self.specifier == other.specifier
and self.url == other.url
and self.marker == other.marker
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,541 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
import logging
import platform
import struct
import subprocess
import sys
import sysconfig
from importlib.machinery import EXTENSION_SUFFIXES
from typing import (
Dict,
FrozenSet,
Iterable,
Iterator,
List,
Optional,
Sequence,
Tuple,
Union,
cast,
)
from . import _manylinux, _musllinux
logger = logging.getLogger(__name__)
PythonVersion = Sequence[int]
MacVersion = Tuple[int, int]
INTERPRETER_SHORT_NAMES: Dict[str, str] = {
"python": "py", # Generic.
"cpython": "cp",
"pypy": "pp",
"ironpython": "ip",
"jython": "jy",
}
_32_BIT_INTERPRETER = struct.calcsize("P") == 4
class Tag:
"""
A representation of the tag triple for a wheel.
Instances are considered immutable and thus are hashable. Equality checking
is also supported.
"""
__slots__ = ["_interpreter", "_abi", "_platform", "_hash"]
def __init__(self, interpreter: str, abi: str, platform: str) -> None:
self._interpreter = interpreter.lower()
self._abi = abi.lower()
self._platform = platform.lower()
# The __hash__ of every single element in a Set[Tag] will be evaluated each time
# that a set calls its `.disjoint()` method, which may be called hundreds of
# times when scanning a page of links for packages with tags matching that
# Set[Tag]. Pre-computing the value here produces significant speedups for
# downstream consumers.
self._hash = hash((self._interpreter, self._abi, self._platform))
@property
def interpreter(self) -> str:
return self._interpreter
@property
def abi(self) -> str:
return self._abi
@property
def platform(self) -> str:
return self._platform
def __eq__(self, other: object) -> bool:
if not isinstance(other, Tag):
return NotImplemented
return (
(self._hash == other._hash) # Short-circuit ASAP for perf reasons.
and (self._platform == other._platform)
and (self._abi == other._abi)
and (self._interpreter == other._interpreter)
)
def __hash__(self) -> int:
return self._hash
def __str__(self) -> str:
return f"{self._interpreter}-{self._abi}-{self._platform}"
def __repr__(self) -> str:
return f"<{self} @ {id(self)}>"
def parse_tag(tag: str) -> FrozenSet[Tag]:
"""
Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances.
Returning a set is required due to the possibility that the tag is a
compressed tag set.
"""
tags = set()
interpreters, abis, platforms = tag.split("-")
for interpreter in interpreters.split("."):
for abi in abis.split("."):
for platform_ in platforms.split("."):
tags.add(Tag(interpreter, abi, platform_))
return frozenset(tags)
def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]:
value: Union[int, str, None] = sysconfig.get_config_var(name)
if value is None and warn:
logger.debug(
"Config variable '%s' is unset, Python ABI tag may be incorrect", name
)
return value
def _normalize_string(string: str) -> str:
return string.replace(".", "_").replace("-", "_").replace(" ", "_")
def _abi3_applies(python_version: PythonVersion) -> bool:
"""
Determine if the Python version supports abi3.
"""
return len(python_version) > 1
def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]:
py_version = tuple(py_version) # To allow for version comparison.
abis = []
version = _version_nodot(py_version[:2])
debug = pymalloc = ucs4 = ""
with_debug = _get_config_var("Py_DEBUG", warn)
has_refcount = hasattr(sys, "gettotalrefcount")
# Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
# extension modules is the best option.
# https://github.com/pypa/pip/issues/3383#issuecomment-173267692
has_ext = "_d.pyd" in EXTENSION_SUFFIXES
if with_debug or (with_debug is None and (has_refcount or has_ext)):
debug = "d"
if debug:
# Debug builds can also load "normal" extension modules.
# We can also assume no UCS-4 or pymalloc requirement.
abis.append(f"cp{version}")
abis.insert(
0,
"cp{version}{debug}{pymalloc}{ucs4}".format(
version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4
),
)
return abis
def cpython_tags(
python_version: Optional[PythonVersion] = None,
abis: Optional[Iterable[str]] = None,
platforms: Optional[Iterable[str]] = None,
*,
warn: bool = False,
) -> Iterator[Tag]:
"""
Yields the tags for a CPython interpreter.
The tags consist of:
- cp<python_version>-<abi>-<platform>
- cp<python_version>-abi3-<platform>
- cp<python_version>-none-<platform>
- cp<less than python_version>-abi3-<platform> # Older Python versions down to 3.2.
If python_version only specifies a major version then user-provided ABIs and
the 'none' ABItag will be used.
If 'abi3' or 'none' are specified in 'abis' then they will be yielded at
their normal position and not at the beginning.
"""
if not python_version:
python_version = sys.version_info[:2]
interpreter = f"cp{_version_nodot(python_version[:2])}"
if abis is None:
if len(python_version) > 1:
abis = _cpython_abis(python_version, warn)
else:
abis = []
abis = list(abis)
# 'abi3' and 'none' are explicitly handled later.
for explicit_abi in ("abi3", "none"):
try:
abis.remove(explicit_abi)
except ValueError:
pass
platforms = list(platforms or platform_tags())
for abi in abis:
for platform_ in platforms:
yield Tag(interpreter, abi, platform_)
if _abi3_applies(python_version):
yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
if _abi3_applies(python_version):
for minor_version in range(python_version[1] - 1, 1, -1):
for platform_ in platforms:
interpreter = "cp{version}".format(
version=_version_nodot((python_version[0], minor_version))
)
yield Tag(interpreter, "abi3", platform_)
def _generic_abi() -> List[str]:
"""
Return the ABI tag based on EXT_SUFFIX.
"""
# The following are examples of `EXT_SUFFIX`.
# We want to keep the parts which are related to the ABI and remove the
# parts which are related to the platform:
# - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310
# - mac: '.cpython-310-darwin.so' => cp310
# - win: '.cp310-win_amd64.pyd' => cp310
# - win: '.pyd' => cp37 (uses _cpython_abis())
# - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73
# - graalpy: '.graalpy-38-native-x86_64-darwin.dylib'
# => graalpy_38_native
ext_suffix = _get_config_var("EXT_SUFFIX", warn=True)
if not isinstance(ext_suffix, str) or ext_suffix[0] != ".":
raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')")
parts = ext_suffix.split(".")
if len(parts) < 3:
# CPython3.7 and earlier uses ".pyd" on Windows.
return _cpython_abis(sys.version_info[:2])
soabi = parts[1]
if soabi.startswith("cpython"):
# non-windows
abi = "cp" + soabi.split("-")[1]
elif soabi.startswith("cp"):
# windows
abi = soabi.split("-")[0]
elif soabi.startswith("pypy"):
abi = "-".join(soabi.split("-")[:2])
elif soabi.startswith("graalpy"):
abi = "-".join(soabi.split("-")[:3])
elif soabi:
# pyston, ironpython, others?
abi = soabi
else:
return []
return [_normalize_string(abi)]
def generic_tags(
interpreter: Optional[str] = None,
abis: Optional[Iterable[str]] = None,
platforms: Optional[Iterable[str]] = None,
*,
warn: bool = False,
) -> Iterator[Tag]:
"""
Yields the tags for a generic interpreter.
The tags consist of:
- <interpreter>-<abi>-<platform>
The "none" ABI will be added if it was not explicitly provided.
"""
if not interpreter:
interp_name = interpreter_name()
interp_version = interpreter_version(warn=warn)
interpreter = "".join([interp_name, interp_version])
if abis is None:
abis = _generic_abi()
else:
abis = list(abis)
platforms = list(platforms or platform_tags())
if "none" not in abis:
abis.append("none")
for abi in abis:
for platform_ in platforms:
yield Tag(interpreter, abi, platform_)
def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
"""
Yields Python versions in descending order.
After the latest version, the major-only version will be yielded, and then
all previous versions of that major version.
"""
if len(py_version) > 1:
yield f"py{_version_nodot(py_version[:2])}"
yield f"py{py_version[0]}"
if len(py_version) > 1:
for minor in range(py_version[1] - 1, -1, -1):
yield f"py{_version_nodot((py_version[0], minor))}"
def compatible_tags(
python_version: Optional[PythonVersion] = None,
interpreter: Optional[str] = None,
platforms: Optional[Iterable[str]] = None,
) -> Iterator[Tag]:
"""
Yields the sequence of tags that are compatible with a specific version of Python.
The tags consist of:
- py*-none-<platform>
- <interpreter>-none-any # ... if `interpreter` is provided.
- py*-none-any
"""
if not python_version:
python_version = sys.version_info[:2]
platforms = list(platforms or platform_tags())
for version in _py_interpreter_range(python_version):
for platform_ in platforms:
yield Tag(version, "none", platform_)
if interpreter:
yield Tag(interpreter, "none", "any")
for version in _py_interpreter_range(python_version):
yield Tag(version, "none", "any")
def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
if not is_32bit:
return arch
if arch.startswith("ppc"):
return "ppc"
return "i386"
def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]:
formats = [cpu_arch]
if cpu_arch == "x86_64":
if version < (10, 4):
return []
formats.extend(["intel", "fat64", "fat32"])
elif cpu_arch == "i386":
if version < (10, 4):
return []
formats.extend(["intel", "fat32", "fat"])
elif cpu_arch == "ppc64":
# TODO: Need to care about 32-bit PPC for ppc64 through 10.2?
if version > (10, 5) or version < (10, 4):
return []
formats.append("fat64")
elif cpu_arch == "ppc":
if version > (10, 6):
return []
formats.extend(["fat32", "fat"])
if cpu_arch in {"arm64", "x86_64"}:
formats.append("universal2")
if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}:
formats.append("universal")
return formats
def mac_platforms(
version: Optional[MacVersion] = None, arch: Optional[str] = None
) -> Iterator[str]:
"""
Yields the platform tags for a macOS system.
The `version` parameter is a two-item tuple specifying the macOS version to
generate platform tags for. The `arch` parameter is the CPU architecture to
generate platform tags for. Both parameters default to the appropriate value
for the current system.
"""
version_str, _, cpu_arch = platform.mac_ver()
if version is None:
version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
if version == (10, 16):
# When built against an older macOS SDK, Python will report macOS 10.16
# instead of the real version.
version_str = subprocess.run(
[
sys.executable,
"-sS",
"-c",
"import platform; print(platform.mac_ver()[0])",
],
check=True,
env={"SYSTEM_VERSION_COMPAT": "0"},
stdout=subprocess.PIPE,
text=True,
).stdout
version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
else:
version = version
if arch is None:
arch = _mac_arch(cpu_arch)
else:
arch = arch
if (10, 0) <= version and version < (11, 0):
# Prior to Mac OS 11, each yearly release of Mac OS bumped the
# "minor" version number. The major version was always 10.
for minor_version in range(version[1], -1, -1):
compat_version = 10, minor_version
binary_formats = _mac_binary_formats(compat_version, arch)
for binary_format in binary_formats:
yield "macosx_{major}_{minor}_{binary_format}".format(
major=10, minor=minor_version, binary_format=binary_format
)
if version >= (11, 0):
# Starting with Mac OS 11, each yearly release bumps the major version
# number. The minor versions are now the midyear updates.
for major_version in range(version[0], 10, -1):
compat_version = major_version, 0
binary_formats = _mac_binary_formats(compat_version, arch)
for binary_format in binary_formats:
yield "macosx_{major}_{minor}_{binary_format}".format(
major=major_version, minor=0, binary_format=binary_format
)
if version >= (11, 0):
# Mac OS 11 on x86_64 is compatible with binaries from previous releases.
# Arm64 support was introduced in 11.0, so no Arm binaries from previous
# releases exist.
#
# However, the "universal2" binary format can have a
# macOS version earlier than 11.0 when the x86_64 part of the binary supports
# that version of macOS.
if arch == "x86_64":
for minor_version in range(16, 3, -1):
compat_version = 10, minor_version
binary_formats = _mac_binary_formats(compat_version, arch)
for binary_format in binary_formats:
yield "macosx_{major}_{minor}_{binary_format}".format(
major=compat_version[0],
minor=compat_version[1],
binary_format=binary_format,
)
else:
for minor_version in range(16, 3, -1):
compat_version = 10, minor_version
binary_format = "universal2"
yield "macosx_{major}_{minor}_{binary_format}".format(
major=compat_version[0],
minor=compat_version[1],
binary_format=binary_format,
)
def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
linux = _normalize_string(sysconfig.get_platform())
if not linux.startswith("linux_"):
# we should never be here, just yield the sysconfig one and return
yield linux
return
if is_32bit:
if linux == "linux_x86_64":
linux = "linux_i686"
elif linux == "linux_aarch64":
linux = "linux_armv8l"
_, arch = linux.split("_", 1)
archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch])
yield from _manylinux.platform_tags(archs)
yield from _musllinux.platform_tags(archs)
for arch in archs:
yield f"linux_{arch}"
def _generic_platforms() -> Iterator[str]:
yield _normalize_string(sysconfig.get_platform())
def platform_tags() -> Iterator[str]:
"""
Provides the platform tags for this installation.
"""
if platform.system() == "Darwin":
return mac_platforms()
elif platform.system() == "Linux":
return _linux_platforms()
else:
return _generic_platforms()
def interpreter_name() -> str:
"""
Returns the name of the running interpreter.
Some implementations have a reserved, two-letter abbreviation which will
be returned when appropriate.
"""
name = sys.implementation.name
return INTERPRETER_SHORT_NAMES.get(name) or name
def interpreter_version(*, warn: bool = False) -> str:
"""
Returns the version of the running interpreter.
"""
version = _get_config_var("py_version_nodot", warn=warn)
if version:
version = str(version)
else:
version = _version_nodot(sys.version_info[:2])
return version
def _version_nodot(version: PythonVersion) -> str:
return "".join(map(str, version))
def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
"""
Returns the sequence of tag triples for the running interpreter.
The order of the sequence corresponds to priority order for the
interpreter, from most to least important.
"""
interp_name = interpreter_name()
if interp_name == "cp":
yield from cpython_tags(warn=warn)
else:
yield from generic_tags()
if interp_name == "pp":
interp = "pp3"
elif interp_name == "cp":
interp = "cp" + interpreter_version(warn=warn)
else:
interp = None
yield from compatible_tags(interpreter=interp)

View file

@ -0,0 +1,172 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
import re
from typing import FrozenSet, NewType, Tuple, Union, cast
from .tags import Tag, parse_tag
from .version import InvalidVersion, Version
BuildTag = Union[Tuple[()], Tuple[int, str]]
NormalizedName = NewType("NormalizedName", str)
class InvalidName(ValueError):
"""
An invalid distribution name; users should refer to the packaging user guide.
"""
class InvalidWheelFilename(ValueError):
"""
An invalid wheel filename was found, users should refer to PEP 427.
"""
class InvalidSdistFilename(ValueError):
"""
An invalid sdist filename was found, users should refer to the packaging user guide.
"""
# Core metadata spec for `Name`
_validate_regex = re.compile(
r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
)
_canonicalize_regex = re.compile(r"[-_.]+")
_normalized_regex = re.compile(r"^([a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9])$")
# PEP 427: The build number must start with a digit.
_build_tag_regex = re.compile(r"(\d+)(.*)")
def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
if validate and not _validate_regex.match(name):
raise InvalidName(f"name is invalid: {name!r}")
# This is taken from PEP 503.
value = _canonicalize_regex.sub("-", name).lower()
return cast(NormalizedName, value)
def is_normalized_name(name: str) -> bool:
return _normalized_regex.match(name) is not None
def canonicalize_version(
version: Union[Version, str], *, strip_trailing_zero: bool = True
) -> str:
"""
This is very similar to Version.__str__, but has one subtle difference
with the way it handles the release segment.
"""
if isinstance(version, str):
try:
parsed = Version(version)
except InvalidVersion:
# Legacy versions cannot be normalized
return version
else:
parsed = version
parts = []
# Epoch
if parsed.epoch != 0:
parts.append(f"{parsed.epoch}!")
# Release segment
release_segment = ".".join(str(x) for x in parsed.release)
if strip_trailing_zero:
# NB: This strips trailing '.0's to normalize
release_segment = re.sub(r"(\.0)+$", "", release_segment)
parts.append(release_segment)
# Pre-release
if parsed.pre is not None:
parts.append("".join(str(x) for x in parsed.pre))
# Post-release
if parsed.post is not None:
parts.append(f".post{parsed.post}")
# Development release
if parsed.dev is not None:
parts.append(f".dev{parsed.dev}")
# Local version segment
if parsed.local is not None:
parts.append(f"+{parsed.local}")
return "".join(parts)
def parse_wheel_filename(
filename: str,
) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]:
if not filename.endswith(".whl"):
raise InvalidWheelFilename(
f"Invalid wheel filename (extension must be '.whl'): {filename}"
)
filename = filename[:-4]
dashes = filename.count("-")
if dashes not in (4, 5):
raise InvalidWheelFilename(
f"Invalid wheel filename (wrong number of parts): {filename}"
)
parts = filename.split("-", dashes - 2)
name_part = parts[0]
# See PEP 427 for the rules on escaping the project name.
if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
raise InvalidWheelFilename(f"Invalid project name: {filename}")
name = canonicalize_name(name_part)
try:
version = Version(parts[1])
except InvalidVersion as e:
raise InvalidWheelFilename(
f"Invalid wheel filename (invalid version): {filename}"
) from e
if dashes == 5:
build_part = parts[2]
build_match = _build_tag_regex.match(build_part)
if build_match is None:
raise InvalidWheelFilename(
f"Invalid build number: {build_part} in '{filename}'"
)
build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2)))
else:
build = ()
tags = parse_tag(parts[-1])
return (name, version, build, tags)
def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]:
if filename.endswith(".tar.gz"):
file_stem = filename[: -len(".tar.gz")]
elif filename.endswith(".zip"):
file_stem = filename[: -len(".zip")]
else:
raise InvalidSdistFilename(
f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):"
f" {filename}"
)
# We are requiring a PEP 440 version, which cannot contain dashes,
# so we split on the last dash.
name_part, sep, version_part = file_stem.rpartition("-")
if not sep:
raise InvalidSdistFilename(f"Invalid sdist filename: {filename}")
name = canonicalize_name(name_part)
try:
version = Version(version_part)
except InvalidVersion as e:
raise InvalidSdistFilename(
f"Invalid sdist filename (invalid version): {filename}"
) from e
return (name, version)

View file

@ -0,0 +1,563 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
"""
.. testsetup::
from packaging.version import parse, Version
"""
import itertools
import re
from typing import Any, Callable, NamedTuple, Optional, SupportsInt, Tuple, Union
from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
__all__ = ["VERSION_PATTERN", "parse", "Version", "InvalidVersion"]
LocalType = Tuple[Union[int, str], ...]
CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]
CmpLocalType = Union[
NegativeInfinityType,
Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],
]
CmpKey = Tuple[
int,
Tuple[int, ...],
CmpPrePostDevType,
CmpPrePostDevType,
CmpPrePostDevType,
CmpLocalType,
]
VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
class _Version(NamedTuple):
epoch: int
release: Tuple[int, ...]
dev: Optional[Tuple[str, int]]
pre: Optional[Tuple[str, int]]
post: Optional[Tuple[str, int]]
local: Optional[LocalType]
def parse(version: str) -> "Version":
"""Parse the given version string.
>>> parse('1.0.dev1')
<Version('1.0.dev1')>
:param version: The version string to parse.
:raises InvalidVersion: When the version string is not a valid version.
"""
return Version(version)
class InvalidVersion(ValueError):
"""Raised when a version string is not a valid version.
>>> Version("invalid")
Traceback (most recent call last):
...
packaging.version.InvalidVersion: Invalid version: 'invalid'
"""
class _BaseVersion:
_key: Tuple[Any, ...]
def __hash__(self) -> int:
return hash(self._key)
# Please keep the duplicated `isinstance` check
# in the six comparisons hereunder
# unless you find a way to avoid adding overhead function calls.
def __lt__(self, other: "_BaseVersion") -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key < other._key
def __le__(self, other: "_BaseVersion") -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key <= other._key
def __eq__(self, other: object) -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key == other._key
def __ge__(self, other: "_BaseVersion") -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key >= other._key
def __gt__(self, other: "_BaseVersion") -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key > other._key
def __ne__(self, other: object) -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key != other._key
# Deliberately not anchored to the start and end of the string, to make it
# easier for 3rd party code to reuse
_VERSION_PATTERN = r"""
v?
(?:
(?:(?P<epoch>[0-9]+)!)? # epoch
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
(?P<pre> # pre-release
[-_\.]?
(?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
[-_\.]?
(?P<pre_n>[0-9]+)?
)?
(?P<post> # post release
(?:-(?P<post_n1>[0-9]+))
|
(?:
[-_\.]?
(?P<post_l>post|rev|r)
[-_\.]?
(?P<post_n2>[0-9]+)?
)
)?
(?P<dev> # dev release
[-_\.]?
(?P<dev_l>dev)
[-_\.]?
(?P<dev_n>[0-9]+)?
)?
)
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
"""
VERSION_PATTERN = _VERSION_PATTERN
"""
A string containing the regular expression used to match a valid version.
The pattern is not anchored at either end, and is intended for embedding in larger
expressions (for example, matching a version number as part of a file name). The
regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
flags set.
:meta hide-value:
"""
class Version(_BaseVersion):
"""This class abstracts handling of a project's versions.
A :class:`Version` instance is comparison aware and can be compared and
sorted using the standard Python interfaces.
>>> v1 = Version("1.0a5")
>>> v2 = Version("1.0")
>>> v1
<Version('1.0a5')>
>>> v2
<Version('1.0')>
>>> v1 < v2
True
>>> v1 == v2
False
>>> v1 > v2
False
>>> v1 >= v2
False
>>> v1 <= v2
True
"""
_regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
_key: CmpKey
def __init__(self, version: str) -> None:
"""Initialize a Version object.
:param version:
The string representation of a version which will be parsed and normalized
before use.
:raises InvalidVersion:
If the ``version`` does not conform to PEP 440 in any way then this
exception will be raised.
"""
# Validate the version and parse it into pieces
match = self._regex.search(version)
if not match:
raise InvalidVersion(f"Invalid version: '{version}'")
# Store the parsed out pieces of the version
self._version = _Version(
epoch=int(match.group("epoch")) if match.group("epoch") else 0,
release=tuple(int(i) for i in match.group("release").split(".")),
pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
post=_parse_letter_version(
match.group("post_l"), match.group("post_n1") or match.group("post_n2")
),
dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
local=_parse_local_version(match.group("local")),
)
# Generate a key which will be used for sorting
self._key = _cmpkey(
self._version.epoch,
self._version.release,
self._version.pre,
self._version.post,
self._version.dev,
self._version.local,
)
def __repr__(self) -> str:
"""A representation of the Version that shows all internal state.
>>> Version('1.0.0')
<Version('1.0.0')>
"""
return f"<Version('{self}')>"
def __str__(self) -> str:
"""A string representation of the version that can be rounded-tripped.
>>> str(Version("1.0a5"))
'1.0a5'
"""
parts = []
# Epoch
if self.epoch != 0:
parts.append(f"{self.epoch}!")
# Release segment
parts.append(".".join(str(x) for x in self.release))
# Pre-release
if self.pre is not None:
parts.append("".join(str(x) for x in self.pre))
# Post-release
if self.post is not None:
parts.append(f".post{self.post}")
# Development release
if self.dev is not None:
parts.append(f".dev{self.dev}")
# Local version segment
if self.local is not None:
parts.append(f"+{self.local}")
return "".join(parts)
@property
def epoch(self) -> int:
"""The epoch of the version.
>>> Version("2.0.0").epoch
0
>>> Version("1!2.0.0").epoch
1
"""
return self._version.epoch
@property
def release(self) -> Tuple[int, ...]:
"""The components of the "release" segment of the version.
>>> Version("1.2.3").release
(1, 2, 3)
>>> Version("2.0.0").release
(2, 0, 0)
>>> Version("1!2.0.0.post0").release
(2, 0, 0)
Includes trailing zeroes but not the epoch or any pre-release / development /
post-release suffixes.
"""
return self._version.release
@property
def pre(self) -> Optional[Tuple[str, int]]:
"""The pre-release segment of the version.
>>> print(Version("1.2.3").pre)
None
>>> Version("1.2.3a1").pre
('a', 1)
>>> Version("1.2.3b1").pre
('b', 1)
>>> Version("1.2.3rc1").pre
('rc', 1)
"""
return self._version.pre
@property
def post(self) -> Optional[int]:
"""The post-release number of the version.
>>> print(Version("1.2.3").post)
None
>>> Version("1.2.3.post1").post
1
"""
return self._version.post[1] if self._version.post else None
@property
def dev(self) -> Optional[int]:
"""The development number of the version.
>>> print(Version("1.2.3").dev)
None
>>> Version("1.2.3.dev1").dev
1
"""
return self._version.dev[1] if self._version.dev else None
@property
def local(self) -> Optional[str]:
"""The local version segment of the version.
>>> print(Version("1.2.3").local)
None
>>> Version("1.2.3+abc").local
'abc'
"""
if self._version.local:
return ".".join(str(x) for x in self._version.local)
else:
return None
@property
def public(self) -> str:
"""The public portion of the version.
>>> Version("1.2.3").public
'1.2.3'
>>> Version("1.2.3+abc").public
'1.2.3'
>>> Version("1.2.3+abc.dev1").public
'1.2.3'
"""
return str(self).split("+", 1)[0]
@property
def base_version(self) -> str:
"""The "base version" of the version.
>>> Version("1.2.3").base_version
'1.2.3'
>>> Version("1.2.3+abc").base_version
'1.2.3'
>>> Version("1!1.2.3+abc.dev1").base_version
'1!1.2.3'
The "base version" is the public version of the project without any pre or post
release markers.
"""
parts = []
# Epoch
if self.epoch != 0:
parts.append(f"{self.epoch}!")
# Release segment
parts.append(".".join(str(x) for x in self.release))
return "".join(parts)
@property
def is_prerelease(self) -> bool:
"""Whether this version is a pre-release.
>>> Version("1.2.3").is_prerelease
False
>>> Version("1.2.3a1").is_prerelease
True
>>> Version("1.2.3b1").is_prerelease
True
>>> Version("1.2.3rc1").is_prerelease
True
>>> Version("1.2.3dev1").is_prerelease
True
"""
return self.dev is not None or self.pre is not None
@property
def is_postrelease(self) -> bool:
"""Whether this version is a post-release.
>>> Version("1.2.3").is_postrelease
False
>>> Version("1.2.3.post1").is_postrelease
True
"""
return self.post is not None
@property
def is_devrelease(self) -> bool:
"""Whether this version is a development release.
>>> Version("1.2.3").is_devrelease
False
>>> Version("1.2.3.dev1").is_devrelease
True
"""
return self.dev is not None
@property
def major(self) -> int:
"""The first item of :attr:`release` or ``0`` if unavailable.
>>> Version("1.2.3").major
1
"""
return self.release[0] if len(self.release) >= 1 else 0
@property
def minor(self) -> int:
"""The second item of :attr:`release` or ``0`` if unavailable.
>>> Version("1.2.3").minor
2
>>> Version("1").minor
0
"""
return self.release[1] if len(self.release) >= 2 else 0
@property
def micro(self) -> int:
"""The third item of :attr:`release` or ``0`` if unavailable.
>>> Version("1.2.3").micro
3
>>> Version("1").micro
0
"""
return self.release[2] if len(self.release) >= 3 else 0
def _parse_letter_version(
letter: Optional[str], number: Union[str, bytes, SupportsInt, None]
) -> Optional[Tuple[str, int]]:
if letter:
# We consider there to be an implicit 0 in a pre-release if there is
# not a numeral associated with it.
if number is None:
number = 0
# We normalize any letters to their lower case form
letter = letter.lower()
# We consider some words to be alternate spellings of other words and
# in those cases we want to normalize the spellings to our preferred
# spelling.
if letter == "alpha":
letter = "a"
elif letter == "beta":
letter = "b"
elif letter in ["c", "pre", "preview"]:
letter = "rc"
elif letter in ["rev", "r"]:
letter = "post"
return letter, int(number)
if not letter and number:
# We assume if we are given a number, but we are not given a letter
# then this is using the implicit post release syntax (e.g. 1.0-1)
letter = "post"
return letter, int(number)
return None
_local_version_separators = re.compile(r"[\._-]")
def _parse_local_version(local: Optional[str]) -> Optional[LocalType]:
"""
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
"""
if local is not None:
return tuple(
part.lower() if not part.isdigit() else int(part)
for part in _local_version_separators.split(local)
)
return None
def _cmpkey(
epoch: int,
release: Tuple[int, ...],
pre: Optional[Tuple[str, int]],
post: Optional[Tuple[str, int]],
dev: Optional[Tuple[str, int]],
local: Optional[LocalType],
) -> CmpKey:
# When we compare a release version, we want to compare it with all of the
# trailing zeros removed. So we'll use a reverse the list, drop all the now
# leading zeros until we come to something non zero, then take the rest
# re-reverse it back into the correct order and make it a tuple and use
# that for our sorting key.
_release = tuple(
reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
)
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
# We'll do this by abusing the pre segment, but we _only_ want to do this
# if there is not a pre or a post segment. If we have one of those then
# the normal sorting rules will handle this case correctly.
if pre is None and post is None and dev is not None:
_pre: CmpPrePostDevType = NegativeInfinity
# Versions without a pre-release (except as noted above) should sort after
# those with one.
elif pre is None:
_pre = Infinity
else:
_pre = pre
# Versions without a post segment should sort before those with one.
if post is None:
_post: CmpPrePostDevType = NegativeInfinity
else:
_post = post
# Versions without a development segment should sort after those with one.
if dev is None:
_dev: CmpPrePostDevType = Infinity
else:
_dev = dev
if local is None:
# Versions without a local segment should sort before those with one.
_local: CmpLocalType = NegativeInfinity
else:
# Versions with a local segment need that segment parsed to implement
# the sorting rules in PEP440.
# - Alpha numeric segments sort before numeric segments
# - Alpha numeric segments sort lexicographically
# - Numeric segments sort numerically
# - Shorter versions sort before longer versions when the prefixes
# match exactly
_local = tuple(
(i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
)
return epoch, _release, _pre, _post, _dev, _local

View file

@ -4,31 +4,33 @@ build-backend = "setuptools.build_meta"
[project]
name = "gyp-next"
version = "0.14.0"
version = "0.22.2"
authors = [
{ name="Node.js contributors", email="ryzokuken@disroot.org" },
]
description = "A fork of the GYP build system for use in the Node.js projects"
readme = "README.md"
license = { file="LICENSE" }
requires-python = ">=3.6"
license = "BSD-3-Clause"
license-files = ["LICENSE"]
requires-python = ">=3.9"
dependencies = ["packaging>=24.0", "setuptools>=77.0.3"]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Natural Language :: English",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
[project.optional-dependencies]
dev = ["flake8", "pytest"]
dev = ["pytest", "ruff"]
[project.scripts]
gyp = "gyp:script_main"
@ -36,6 +38,79 @@ gyp = "gyp:script_main"
[project.urls]
"Homepage" = "https://github.com/nodejs/gyp-next"
[tool.ruff]
extend-exclude = ["pylib/packaging"]
line-length = 88
[tool.ruff.lint]
select = [
"C4", # flake8-comprehensions
"C90", # McCabe cyclomatic complexity
"DTZ", # flake8-datetimez
"E", # pycodestyle
"F", # Pyflakes
"G", # flake8-logging-format
"ICN", # flake8-import-conventions
"INT", # flake8-gettext
"PL", # Pylint
"PYI", # flake8-pyi
"RSE", # flake8-raise
"RUF", # Ruff-specific rules
"T10", # flake8-debugger
"TCH", # flake8-type-checking
"TID", # flake8-tidy-imports
"UP", # pyupgrade
"W", # pycodestyle
"YTT", # flake8-2020
# "A", # flake8-builtins
# "ANN", # flake8-annotations
# "ARG", # flake8-unused-arguments
# "B", # flake8-bugbear
# "BLE", # flake8-blind-except
# "COM", # flake8-commas
# "D", # pydocstyle
# "DJ", # flake8-django
# "EM", # flake8-errmsg
# "ERA", # eradicate
# "EXE", # flake8-executable
# "FBT", # flake8-boolean-trap
# "I", # isort
# "INP", # flake8-no-pep420
# "ISC", # flake8-implicit-str-concat
# "N", # pep8-naming
# "NPY", # NumPy-specific rules
# "PD", # pandas-vet
# "PGH", # pygrep-hooks
# "PIE", # flake8-pie
# "PT", # flake8-pytest-style
# "PTH", # flake8-use-pathlib
# "Q", # flake8-quotes
# "RET", # flake8-return
# "S", # flake8-bandit
# "SIM", # flake8-simplify
# "SLF", # flake8-self
# "T20", # flake8-print
# "TRY", # tryceratops
]
ignore = [
"PLR1714",
"PLW0603",
"PLW2901",
"RUF005",
"RUF012",
"UP031",
]
[tool.ruff.lint.mccabe]
max-complexity = 101
[tool.ruff.lint.pylint]
allow-magic-value-types = ["float", "int", "str"]
max-args = 11
max-branches = 108
max-returns = 10
max-statements = 286
[tool.setuptools]
package-dir = {"" = "pylib"}
packages = ["gyp", "gyp.generator"]

View file

@ -0,0 +1,11 @@
{
"last-release-sha": "78756421b0d7bb335992a9c7d26ba3cc8b619708",
"packages": {
".": {
"release-type": "python",
"package-name": "gyp-next",
"bump-minor-pre-major": true,
"include-component-in-tag": false
}
}
}

View file

@ -5,7 +5,6 @@
"""gyptest.py -- test runner for GYP tests."""
import argparse
import os
import platform
@ -148,13 +147,13 @@ def print_configuration_info():
print("Test configuration:")
if sys.platform == "darwin":
sys.path.append(os.path.abspath("test/lib"))
import TestMac
import TestMac # noqa: PLC0415
print(f" Mac {platform.mac_ver()[0]} {platform.mac_ver()[2]}")
print(f" Xcode {TestMac.Xcode.Version()}")
elif sys.platform == "win32":
sys.path.append(os.path.abspath("pylib"))
import gyp.MSVSVersion
import gyp.MSVSVersion # noqa: PLC0415
print(" Win %s %s\n" % platform.win32_ver()[0:2])
print(" MSVS %s" % gyp.MSVSVersion.SelectVisualStudioVersion().Description())

Some files were not shown because too many files have changed in this diff Show more