diff --git a/.github/workflows/e2e-cache.yml b/.github/workflows/e2e-cache.yml index ce3366a..0aea4b7 100644 --- a/.github/workflows/e2e-cache.yml +++ b/.github/workflows/e2e-cache.yml @@ -39,7 +39,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.9', 'pypy-3.7-v7.x'] + python-version: ['3.9', 'pypy-3.9-v7.x'] steps: - uses: actions/checkout@v3 - name: Setup Python @@ -48,11 +48,17 @@ jobs: python-version: ${{ matrix.python-version }} cache: 'pipenv' - name: Install pipenv - run: pipx install pipenv + run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python - name: Install dependencies + shell: pwsh run: | - cd __tests__/data - pipenv install --verbose + mv ./__tests__/data/Pipfile.lock . + mv ./__tests__/data/Pipfile . + if ("${{ matrix.python-version }}" -Match "pypy") { + pipenv install --keep-outdated --python pypy + } else { + pipenv install --keep-outdated --python ${{ matrix.python-version }} + } python-poetry-dependencies-caching: name: Test poetry (Python ${{ matrix.python-version}}, ${{ matrix.os }}) @@ -102,7 +108,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.9', 'pypy-3.7-v7.x'] + python-version: ['3.9', 'pypy-3.9-v7.x'] steps: - uses: actions/checkout@v3 - name: Setup Python @@ -112,8 +118,14 @@ jobs: cache: 'pipenv' cache-dependency-path: '**/pipenv-requirements.txt' - name: Install pipenv - run: pipx install pipenv + run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python - name: Install dependencies + shell: pwsh run: | - cd __tests__/data - pipenv install --verbose \ No newline at end of file + mv ./__tests__/data/Pipfile.lock . + mv ./__tests__/data/Pipfile . + if ("${{ matrix.python-version }}" -Match "pypy") { + pipenv install --keep-outdated --python pypy + } else { + pipenv install --keep-outdated --python ${{ matrix.python-version }} + } \ No newline at end of file diff --git a/.github/workflows/test-pypy.yml b/.github/workflows/test-pypy.yml index 680701f..2c1d960 100644 --- a/.github/workflows/test-pypy.yml +++ b/.github/workflows/test-pypy.yml @@ -66,6 +66,32 @@ jobs: ${EXECUTABLE} --version shell: bash + setup-pypy-noenv: + name: Setup PyPy ${{ matrix.pypy }} ${{ matrix.os }} (noenv) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest, ubuntu-18.04, ubuntu-latest] + pypy: ['pypy2.7', 'pypy3.7', 'pypy3.8', 'pypy3.9-nightly'] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: setup-python ${{ matrix.pypy }} + id: setup-python + uses: ./ + with: + python-version: ${{ matrix.pypy }} + update-environment: false + + - name: PyPy and Python version + run: ${{ steps.setup-python.outputs.python-path }} --version + + - name: Run simple code + run: ${{ steps.setup-python.outputs.python-path }} -c 'import math; print(math.factorial(5))' + check-latest: runs-on: ${{ matrix.os }} strategy: @@ -81,7 +107,7 @@ jobs: check-latest: true - name: PyPy and Python version run: python --version - + - name: Run simple code run: python -c 'import math; print(math.factorial(5))' diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 31fb9c9..921449f 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -148,6 +148,31 @@ jobs: - name: Run simple code run: python -c 'import math; print(math.factorial(5))' + setup-versions-noenv: + name: Setup ${{ matrix.python }} ${{ matrix.os }} (noenv) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest, ubuntu-18.04, ubuntu-20.04] + python: ["3.7", "3.8", "3.9", "3.10"] + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: setup-python ${{ matrix.python }} + id: setup-python + uses: ./ + with: + python-version: ${{ matrix.python }} + update-environment: false + + - name: Python version + run: ${{ steps.setup-python.outputs.python-path }} --version + + - name: Run simple code + run: ${{ steps.setup-python.outputs.python-path }} -c 'import math; print(math.factorial(5))' + check-latest: runs-on: ${{ matrix.os }} strategy: diff --git a/.licenses/npm/@actions/cache.dep.yml b/.licenses/npm/@actions/cache.dep.yml index f7aa583..c3c245e 100644 --- a/.licenses/npm/@actions/cache.dep.yml +++ b/.licenses/npm/@actions/cache.dep.yml @@ -1,6 +1,6 @@ --- name: "@actions/cache" -version: 2.0.2 +version: 3.0.0 type: npm summary: Actions cache lib homepage: https://github.com/actions/toolkit/tree/main/packages/cache diff --git a/.licenses/npm/@actions/http-client-2.0.1.dep.yml b/.licenses/npm/@actions/http-client-2.0.1.dep.yml new file mode 100644 index 0000000..88e4e66 --- /dev/null +++ b/.licenses/npm/@actions/http-client-2.0.1.dep.yml @@ -0,0 +1,32 @@ +--- +name: "@actions/http-client" +version: 2.0.1 +type: npm +summary: Actions Http Client +homepage: https://github.com/actions/http-client#readme +license: mit +licenses: +- sources: LICENSE + text: | + Actions Http Client for Node.js + + Copyright (c) GitHub, Inc. + + All rights reserved. + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +notices: [] diff --git a/README.md b/README.md index b563bd6..cb17004 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,26 @@ steps: - run: python my_script.py ``` +Download and set up the latest patch version of Python (for specified major & minor versions): +```yaml +steps: +- uses: actions/checkout@v3 +- uses: actions/setup-python@v4 + with: + python-version: '3.11-dev' +- run: python my_script.py +``` + +Download and set up the latest stable version of Python (for specified major version): +```yaml +steps: +- uses: actions/checkout@v3 +- uses: actions/setup-python@v4 + with: + python-version: '3.x' +- run: python my_script.py +``` + Download and set up PyPy: ```yaml @@ -287,12 +307,12 @@ steps: ```yaml steps: - uses: actions/checkout@v3 -- name: Install pipenv - run: pipx install pipenv - uses: actions/setup-python@v4 with: python-version: '3.9' cache: 'pipenv' +- name: Install pipenv + run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python - run: pipenv install ``` @@ -326,8 +346,6 @@ steps: ```yaml steps: - uses: actions/checkout@v3 -- name: Install pipenv - run: pipx install pipenv - uses: actions/setup-python@v4 with: python-version: '3.9' @@ -335,9 +353,46 @@ steps: cache-dependency-path: | server/app/Pipfile.lock __test__/app/Pipfile.lock +- name: Install pipenv + run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python - run: pipenv install ``` +**Using a list of wildcard patterns to cache dependencies** +```yaml +steps: +- uses: actions/checkout@v3 +- uses: actions/setup-python@v4 + with: + python-version: '3.10' + cache: 'pip' + cache-dependency-path: | + **/setup.cfg + **/requirements*.txt +- run: pip install -e . -r subdirectory/requirements-dev.txt +``` + + +# Environment variables + + The `update-environment` flag defaults to `true`. + With this setting, the action will add/update environment variables (e.g. `PATH`, `PKG_CONFIG_PATH`, `pythonLocation`) for `python` to just work out of the box. + + If `update-environment` is set to `false`, the action will not add/update environment variables. + This can prove useful if you want the only side-effect to be to ensure python is installed and rely on the `python-path` output to run python. + Such a requirement on side-effect could be because you don't want your composite action messing with your user's workflows. + + ```yaml + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + id: cp310 + with: + python-version: '3.10' + update-environment: false + - run: ${{ steps.cp310.outputs.python-path }} my_script.py + ``` + # Using `setup-python` with a self hosted runner Python distributions are only available for the same [environments](https://github.com/actions/virtual-environments#available-environments) that GitHub Actions hosted environments are available for. If you are using an unsupported version of Ubuntu such as `19.04` or another Linux distribution such as Fedora, `setup-python` will not work. If you have a supported self-hosted runner and you would like to use `setup-python`, there are a few extra things you need to make sure are set up so that new versions of Python can be downloaded and configured on your runner. @@ -355,7 +410,7 @@ If you are experiencing problems while configuring Python on your self-hosted ru ### Linux - The Python packages that are downloaded from `actions/python-versions` are originally compiled from source in `/opt/hostedtoolcache/` with the [--enable-shared](https://github.com/actions/python-versions/blob/94f04ae6806c6633c82db94c6406a16e17decd5c/builders/ubuntu-python-builder.psm1#L35) flag, which makes them non-relocatable. -- Create an environment variable called `AGENT_TOOLSDIRECTORY` and set it to `/opt/hostedtoolcache`. This controls where the runner downloads and installs tools. +- By default runner downloads and install the tools to `/opt/hostedtoolcache`. The environment variable called `AGENT_TOOLSDIRECTORY` can be set to change this location. - In the same shell that your runner is using, type `export AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache`. - A more permanent way of setting the environment variable is to create a `.env` file in the same directory as your runner and to add `AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache`. This ensures the variable is always set if your runner is configured as a service. - Create a directory called `hostedtoolcache` inside `/opt`. @@ -384,14 +439,7 @@ If you are experiencing problems while configuring Python on your self-hosted ru `setup-python` comes pre-installed on the appliance with GHES if Actions is enabled. When dynamically downloading Python distributions, `setup-python` downloads distributions from [`actions/python-versions`](https://github.com/actions/python-versions) on github.com (outside of the appliance). These calls to `actions/python-versions` are made via unauthenticated requests, which are limited to [60 requests per hour per IP](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting). If more requests are made within the time frame, then you will start to see rate-limit errors during download that read `##[error]API rate limit exceeded for...`. -To get a higher rate limit, you can [generate a personal access token on github.com](https://github.com/settings/tokens/new) and pass it as the `token` input for the action: - -```yml -uses: actions/setup-python@v4 -with: - token: ${{ secrets.GH_DOTCOM_TOKEN }} - python-version: 3.11 -``` +To avoid hitting rate-limit problems, we recommend [setting up your own runner tool cache](https://docs.github.com/en/enterprise-server@2.22/admin/github-actions/managing-access-to-actions-from-githubcom/setting-up-the-tool-cache-on-self-hosted-runners-without-internet-access#about-the-included-setup-actions-and-the-runner-tool-cache). # License diff --git a/__tests__/cache-restore.test.ts b/__tests__/cache-restore.test.ts index 99b674e..573f36a 100644 --- a/__tests__/cache-restore.test.ts +++ b/__tests__/cache-restore.test.ts @@ -5,7 +5,7 @@ import {getCacheDistributor} from '../src/cache-distributions/cache-factory'; describe('restore-cache', () => { const pipFileLockHash = - 'd1dd6218299d8a6db5fc2001d988b34a8b31f1e9d0bb4534d377dde7c19f64b3'; + 'a3bdcc71289e4979ca9e051810d81999cc99823109faf6912e17ff14c8e621a6'; const requirementsHash = 'd8110e0006d7fb5ee76365d565eef9d37df1d11598b912d3eb66d398d57a1121'; const requirementsLinuxHash = diff --git a/__tests__/cache-save.test.ts b/__tests__/cache-save.test.ts index 3fad9a2..f90ca7f 100644 --- a/__tests__/cache-save.test.ts +++ b/__tests__/cache-save.test.ts @@ -212,6 +212,59 @@ describe('run', () => { ); expect(setFailedSpy).not.toHaveBeenCalled(); }); + + it('saves with -1 cacheId , should not fail workflow', async () => { + inputs['cache'] = 'poetry'; + getStateSpy.mockImplementation((name: string) => { + if (name === State.STATE_CACHE_PRIMARY_KEY) { + return poetryLockHash; + } else if (name === State.CACHE_PATHS) { + return JSON.stringify([__dirname]); + } else { + return requirementsHash; + } + }); + + saveCacheSpy.mockImplementation(() => { + return -1; + }); + + await run(); + + expect(getInputSpy).toHaveBeenCalled(); + expect(getStateSpy).toHaveBeenCalledTimes(3); + expect(infoSpy).not.toHaveBeenCalled(); + expect(saveCacheSpy).toHaveBeenCalled(); + expect(infoSpy).not.toHaveBeenLastCalledWith( + `Cache saved with the key: ${poetryLockHash}` + ); + expect(setFailedSpy).not.toHaveBeenCalled(); + }); + + it('saves with error from toolkit, should fail workflow', async () => { + inputs['cache'] = 'npm'; + getStateSpy.mockImplementation((name: string) => { + if (name === State.STATE_CACHE_PRIMARY_KEY) { + return poetryLockHash; + } else if (name === State.CACHE_PATHS) { + return JSON.stringify([__dirname]); + } else { + return requirementsHash; + } + }); + + saveCacheSpy.mockImplementation(() => { + throw new cache.ValidationError('Validation failed'); + }); + + await run(); + + expect(getInputSpy).toHaveBeenCalled(); + expect(getStateSpy).toHaveBeenCalledTimes(3); + expect(infoSpy).not.toHaveBeenCalledWith(); + expect(saveCacheSpy).toHaveBeenCalled(); + expect(setFailedSpy).toHaveBeenCalled(); + }); }); afterEach(() => { diff --git a/__tests__/data/Pipfile b/__tests__/data/Pipfile index 8208ab3..8db5455 100644 --- a/__tests__/data/Pipfile +++ b/__tests__/data/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -numpy = "1.22.3" -pandas = "1.4.2" +flake8 = "==4.0.1" +numpy = "==1.23.0" [dev-packages] diff --git a/__tests__/data/Pipfile.lock b/__tests__/data/Pipfile.lock index 5a67a8f..9fcecef 100644 --- a/__tests__/data/Pipfile.lock +++ b/__tests__/data/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "33e3640eff8b2b6c7149b85568151f39a66c544033b4b3f3f2ec9ad5ce6dfe7e" + "sha256": "e9c37110984955621040e2dc8548c026eb8466c23db1b8e69430289b10be8938" }, "pipfile-spec": 6, "requires": { @@ -16,81 +16,64 @@ ] }, "default": { + "flake8": { + "hashes": [ + "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d", + "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d" + ], + "index": "pypi", + "version": "==4.0.1" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, "numpy": { "hashes": [ - "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676", - "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4", - "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce", - "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123", - "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1", - "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e", - "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5", - "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d", - "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a", - "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab", - "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75", - "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168", - "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4", - "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f", - "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18", - "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62", - "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe", - "sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430", - "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802", - "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa" + "sha256:092f5e6025813e64ad6d1b52b519165d08c730d099c114a9247c9bb635a2a450", + "sha256:196cd074c3f97c4121601790955f915187736f9cf458d3ee1f1b46aff2b1ade0", + "sha256:1c29b44905af288b3919803aceb6ec7fec77406d8b08aaa2e8b9e63d0fe2f160", + "sha256:2b2da66582f3a69c8ce25ed7921dcd8010d05e59ac8d89d126a299be60421171", + "sha256:5043bcd71fcc458dfb8a0fc5509bbc979da0131b9d08e3d5f50fb0bbb36f169a", + "sha256:58bfd40eb478f54ff7a5710dd61c8097e169bc36cc68333d00a9bcd8def53b38", + "sha256:79a506cacf2be3a74ead5467aee97b81fca00c9c4c8b3ba16dbab488cd99ba10", + "sha256:94b170b4fa0168cd6be4becf37cb5b127bd12a795123984385b8cd4aca9857e5", + "sha256:97a76604d9b0e79f59baeca16593c711fddb44936e40310f78bfef79ee9a835f", + "sha256:98e8e0d8d69ff4d3fa63e6c61e8cfe2d03c29b16b58dbef1f9baa175bbed7860", + "sha256:ac86f407873b952679f5f9e6c0612687e51547af0e14ddea1eedfcb22466babd", + "sha256:ae8adff4172692ce56233db04b7ce5792186f179c415c37d539c25de7298d25d", + "sha256:bd3fa4fe2e38533d5336e1272fc4e765cabbbde144309ccee8675509d5cd7b05", + "sha256:d0d2094e8f4d760500394d77b383a1b06d3663e8892cdf5df3c592f55f3bff66", + "sha256:d54b3b828d618a19779a84c3ad952e96e2c2311b16384e973e671aa5be1f6187", + "sha256:d6ca8dabe696c2785d0c8c9b0d8a9b6e5fdbe4f922bde70d57fa1a2848134f95", + "sha256:d8cc87bed09de55477dba9da370c1679bd534df9baa171dd01accbb09687dac3", + "sha256:f0f18804df7370571fb65db9b98bf1378172bd4e962482b857e612d1fec0f53e", + "sha256:f1d88ef79e0a7fa631bb2c3dda1ea46b32b1fe614e10fedd611d3d5398447f2f", + "sha256:f9c3fc2adf67762c9fe1849c859942d23f8d3e0bee7b5ed3d4a9c3eeb50a2f07", + "sha256:fc431493df245f3c627c0c05c2bd134535e7929dbe2e602b80e42bf52ff760bc", + "sha256:fe8b9683eb26d2c4d5db32cd29b38fdcf8381324ab48313b5b69088e0e355379" ], "index": "pypi", - "version": "==1.22.3" + "version": "==1.23.0" }, - "pandas": { + "pycodestyle": { "hashes": [ - "sha256:0010771bd9223f7afe5f051eb47c4a49534345dfa144f2f5470b27189a4dd3b5", - "sha256:061609334a8182ab500a90fe66d46f6f387de62d3a9cb9aa7e62e3146c712167", - "sha256:09d8be7dd9e1c4c98224c4dfe8abd60d145d934e9fc1f5f411266308ae683e6a", - "sha256:295872bf1a09758aba199992c3ecde455f01caf32266d50abc1a073e828a7b9d", - "sha256:3228198333dd13c90b6434ddf61aa6d57deaca98cf7b654f4ad68a2db84f8cfe", - "sha256:385c52e85aaa8ea6a4c600a9b2821181a51f8be0aee3af6f2dcb41dafc4fc1d0", - "sha256:51649ef604a945f781105a6d2ecf88db7da0f4868ac5d45c51cb66081c4d9c73", - "sha256:5586cc95692564b441f4747c47c8a9746792e87b40a4680a2feb7794defb1ce3", - "sha256:5a206afa84ed20e07603f50d22b5f0db3fb556486d8c2462d8bc364831a4b417", - "sha256:5b79af3a69e5175c6fa7b4e046b21a646c8b74e92c6581a9d825687d92071b51", - "sha256:5c54ea4ef3823108cd4ec7fb27ccba4c3a775e0f83e39c5e17f5094cb17748bc", - "sha256:8c5bf555b6b0075294b73965adaafb39cf71c312e38c5935c93d78f41c19828a", - "sha256:92bc1fc585f1463ca827b45535957815b7deb218c549b7c18402c322c7549a12", - "sha256:95c1e422ced0199cf4a34385ff124b69412c4bc912011ce895582bee620dfcaa", - "sha256:b8134651258bce418cb79c71adeff0a44090c98d955f6953168ba16cc285d9f7", - "sha256:be67c782c4f1b1f24c2f16a157e12c2693fd510f8df18e3287c77f33d124ed07", - "sha256:c072c7f06b9242c855ed8021ff970c0e8f8b10b35e2640c657d2a541c5950f59", - "sha256:d0d4f13e4be7ce89d7057a786023c461dd9370040bdb5efa0a7fe76b556867a0", - "sha256:df82739e00bb6daf4bba4479a40f38c718b598a84654cbd8bb498fd6b0aa8c16", - "sha256:f549097993744ff8c41b5e8f2f0d3cbfaabe89b4ae32c8c08ead6cc535b80139", - "sha256:ff08a14ef21d94cdf18eef7c569d66f2e24e0bc89350bcd7d243dd804e3b5eb2" + "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", + "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f" ], - "index": "pypi", - "version": "==1.4.2" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.8.0" }, - "python-dateutil": { + "pyflakes": { "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c", + "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==2.8.2" - }, - "pytz": { - "hashes": [ - "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", - "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" - ], - "version": "==2022.1" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.4.0" } }, "develop": {} diff --git a/__tests__/find-pypy.test.ts b/__tests__/find-pypy.test.ts index 81f1442..660f23d 100644 --- a/__tests__/find-pypy.test.ts +++ b/__tests__/find-pypy.test.ts @@ -179,6 +179,9 @@ describe('findPyPyVersion', () => { let spyWriteExactPyPyVersionFile: jest.SpyInstance; let spyCacheDir: jest.SpyInstance; let spyChmodSync: jest.SpyInstance; + let spyCoreAddPath: jest.SpyInstance; + let spyCoreExportVariable: jest.SpyInstance; + const env = process.env; beforeEach(() => { getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput'); @@ -202,6 +205,8 @@ describe('findPyPyVersion', () => { setOutputSpy = jest.spyOn(core, 'setOutput'); setOutputSpy.mockImplementation(() => null); + jest.resetModules(); + process.env = {...env}; tcFind = jest.spyOn(tc, 'find'); tcFind.mockImplementation((tool: string, version: string) => { const semverRange = new semver.Range(version); @@ -253,32 +258,46 @@ describe('findPyPyVersion', () => { spyExistsSync = jest.spyOn(fs, 'existsSync'); spyExistsSync.mockReturnValue(true); + + spyCoreAddPath = jest.spyOn(core, 'addPath'); + + spyCoreExportVariable = jest.spyOn(core, 'exportVariable'); }); afterEach(() => { jest.resetAllMocks(); jest.clearAllMocks(); jest.restoreAllMocks(); + process.env = env; }); it('found PyPy in toolcache', async () => { await expect( - finder.findPyPyVersion('pypy-3.6-v7.3.x', architecture, false) + finder.findPyPyVersion('pypy-3.6-v7.3.x', architecture, true, false) ).resolves.toEqual({ resolvedPythonVersion: '3.6.12', resolvedPyPyVersion: '7.3.3' }); + expect(spyCoreAddPath).toHaveBeenCalled(); + expect(spyCoreExportVariable).toHaveBeenCalledWith( + 'pythonLocation', + expect.anything() + ); + expect(spyCoreExportVariable).toHaveBeenCalledWith( + 'PKG_CONFIG_PATH', + expect.anything() + ); }); it('throw on invalid input format', async () => { await expect( - finder.findPyPyVersion('pypy3.7-v7.3.x', architecture, false) + finder.findPyPyVersion('pypy3.7-v7.3.x', architecture, true, false) ).rejects.toThrow(); }); it('throw on invalid input format pypy3.7-7.3.x', async () => { await expect( - finder.findPyPyVersion('pypy3.7-v7.3.x', architecture, false) + finder.findPyPyVersion('pypy3.7-v7.3.x', architecture, true, false) ).rejects.toThrow(); }); @@ -290,16 +309,42 @@ describe('findPyPyVersion', () => { spyChmodSync = jest.spyOn(fs, 'chmodSync'); spyChmodSync.mockImplementation(() => undefined); await expect( - finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture, false) + finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture, true, false) ).resolves.toEqual({ resolvedPythonVersion: '3.7.9', resolvedPyPyVersion: '7.3.3' }); + expect(spyCoreAddPath).toHaveBeenCalled(); + expect(spyCoreExportVariable).toHaveBeenCalledWith( + 'pythonLocation', + expect.anything() + ); + expect(spyCoreExportVariable).toHaveBeenCalledWith( + 'PKG_CONFIG_PATH', + expect.anything() + ); + }); + + it('found and install successfully without environment update', async () => { + spyCacheDir = jest.spyOn(tc, 'cacheDir'); + spyCacheDir.mockImplementation(() => + path.join(toolDir, 'PyPy', '3.7.7', architecture) + ); + spyChmodSync = jest.spyOn(fs, 'chmodSync'); + spyChmodSync.mockImplementation(() => undefined); + await expect( + finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture, false, false) + ).resolves.toEqual({ + resolvedPythonVersion: '3.7.9', + resolvedPyPyVersion: '7.3.3' + }); + expect(spyCoreAddPath).not.toHaveBeenCalled(); + expect(spyCoreExportVariable).not.toHaveBeenCalled(); }); it('throw if release is not found', async () => { await expect( - finder.findPyPyVersion('pypy-3.7-v7.5.x', architecture, false) + finder.findPyPyVersion('pypy-3.7-v7.5.x', architecture, true, false) ).rejects.toThrowError( `PyPy version 3.7 (v7.5.x) with arch ${architecture} not found` ); @@ -307,7 +352,7 @@ describe('findPyPyVersion', () => { it('check-latest enabled version found and used from toolcache', async () => { await expect( - finder.findPyPyVersion('pypy-3.6-v7.3.x', architecture, true) + finder.findPyPyVersion('pypy-3.6-v7.3.x', architecture, false, true) ).resolves.toEqual({ resolvedPythonVersion: '3.6.12', resolvedPyPyVersion: '7.3.3' @@ -326,7 +371,7 @@ describe('findPyPyVersion', () => { spyChmodSync = jest.spyOn(fs, 'chmodSync'); spyChmodSync.mockImplementation(() => undefined); await expect( - finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture, true) + finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture, false, true) ).resolves.toEqual({ resolvedPythonVersion: '3.7.9', resolvedPyPyVersion: '7.3.3' @@ -346,7 +391,7 @@ describe('findPyPyVersion', () => { return pypyPath; }); await expect( - finder.findPyPyVersion('pypy-3.8-v7.3.x', architecture, true) + finder.findPyPyVersion('pypy-3.8-v7.3.x', architecture, false, true) ).resolves.toEqual({ resolvedPythonVersion: '3.8.8', resolvedPyPyVersion: '7.3.3' diff --git a/__tests__/finder.test.ts b/__tests__/finder.test.ts index 862f44e..d21e8d3 100644 --- a/__tests__/finder.test.ts +++ b/__tests__/finder.test.ts @@ -28,16 +28,24 @@ const manifestData = require('./data/versions-manifest.json'); describe('Finder tests', () => { let writeSpy: jest.SpyInstance; + let spyCoreAddPath: jest.SpyInstance; + let spyCoreExportVariable: jest.SpyInstance; + const env = process.env; beforeEach(() => { writeSpy = jest.spyOn(process.stdout, 'write'); writeSpy.mockImplementation(() => {}); + jest.resetModules(); + process.env = {...env}; + spyCoreAddPath = jest.spyOn(core, 'addPath'); + spyCoreExportVariable = jest.spyOn(core, 'exportVariable'); }); afterEach(() => { jest.resetAllMocks(); jest.clearAllMocks(); jest.restoreAllMocks(); + process.env = env; }); it('Finds Python if it is installed', async () => { @@ -48,7 +56,26 @@ describe('Finder tests', () => { await io.mkdirP(pythonDir); fs.writeFileSync(`${pythonDir}.complete`, 'hello'); // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) - await finder.useCpythonVersion('3.x', 'x64', false); + await finder.useCpythonVersion('3.x', 'x64', true, false); + expect(spyCoreAddPath).toHaveBeenCalled(); + expect(spyCoreExportVariable).toHaveBeenCalledWith( + 'pythonLocation', + expect.anything() + ); + expect(spyCoreExportVariable).toHaveBeenCalledWith( + 'PKG_CONFIG_PATH', + expect.anything() + ); + }); + + it('Finds Python if it is installed without environment update', async () => { + const pythonDir: string = path.join(toolDir, 'Python', '3.0.0', 'x64'); + await io.mkdirP(pythonDir); + fs.writeFileSync(`${pythonDir}.complete`, 'hello'); + // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) + await finder.useCpythonVersion('3.x', 'x64', false, false); + expect(spyCoreAddPath).not.toHaveBeenCalled(); + expect(spyCoreExportVariable).not.toHaveBeenCalled(); }); it('Finds stable Python version if it is not installed, but exists in the manifest', async () => { @@ -68,7 +95,16 @@ describe('Finder tests', () => { fs.writeFileSync(`${pythonDir}.complete`, 'hello'); }); // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) - await finder.useCpythonVersion('1.2.3', 'x64', false); + await finder.useCpythonVersion('1.2.3', 'x64', true, false); + expect(spyCoreAddPath).toHaveBeenCalled(); + expect(spyCoreExportVariable).toHaveBeenCalledWith( + 'pythonLocation', + expect.anything() + ); + expect(spyCoreExportVariable).toHaveBeenCalledWith( + 'PKG_CONFIG_PATH', + expect.anything() + ); }); it('Finds pre-release Python version in the manifest', async () => { @@ -93,7 +129,7 @@ describe('Finder tests', () => { fs.writeFileSync(`${pythonDir}.complete`, 'hello'); }); // This will throw if it doesn't find it in the manifest (because no such version exists) - await finder.useCpythonVersion('1.2.3-beta.2', 'x64', false); + await finder.useCpythonVersion('1.2.3-beta.2', 'x64', false, false); }); it('Check-latest true, finds the latest version in the manifest', async () => { @@ -140,7 +176,7 @@ describe('Finder tests', () => { fs.writeFileSync(`${pythonDir}.complete`, 'hello'); // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) - await finder.useCpythonVersion('1.2', 'x64', true); + await finder.useCpythonVersion('1.2', 'x64', false, true); expect(infoSpy).toHaveBeenCalledWith("Resolved as '1.2.3'"); expect(infoSpy).toHaveBeenCalledWith( @@ -151,16 +187,28 @@ describe('Finder tests', () => { ); expect(installSpy).toHaveBeenCalled(); expect(addPathSpy).toHaveBeenCalledWith(expPath); + await finder.useCpythonVersion('1.2.3-beta.2', 'x64', false, true); + expect(spyCoreAddPath).toHaveBeenCalled(); + expect(spyCoreExportVariable).toHaveBeenCalledWith( + 'pythonLocation', + expect.anything() + ); + expect(spyCoreExportVariable).toHaveBeenCalledWith( + 'PKG_CONFIG_PATH', + expect.anything() + ); }); it('Errors if Python is not installed', async () => { // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) let thrown = false; try { - await finder.useCpythonVersion('3.300000', 'x64', false); + await finder.useCpythonVersion('3.300000', 'x64', true, false); } catch { thrown = true; } expect(thrown).toBeTruthy(); + expect(spyCoreAddPath).not.toHaveBeenCalled(); + expect(spyCoreExportVariable).not.toHaveBeenCalled(); }); }); diff --git a/action.yml b/action.yml index 1ae83cd..f4aeb35 100644 --- a/action.yml +++ b/action.yml @@ -16,10 +16,13 @@ inputs: description: 'Set this option if you want the action to check for the latest available version that satisfies the version spec.' default: false token: - description: The token used to authenticate when to fetching Python distributions from https://github.com/actions/python-versions. When running this action on github.com, the default value is sufficient. When running on GHES, you can pass a personal access token for github.com if you are experiencing rate limiting. + description: Used to pull python distributions from actions/python-versions. Since there's a default, this is typically not supplied by the user. default: ${{ github.token }} cache-dependency-path: description: 'Used to specify the path to dependency files. Supports wildcards or a list of file names for caching multiple dependencies.' + update-environment: + description: 'Set this option if you want the action to update environment variables.' + default: true outputs: python-version: description: "The installed python version. Useful when given a version range as input." diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js index 7c0104e..b3fd8a7 100644 --- a/dist/cache-save/index.js +++ b/dist/cache-save/index.js @@ -90,17 +90,18 @@ function restoreCache(paths, primaryKey, restoreKeys, options) { checkKey(key); } const compressionMethod = yield utils.getCompressionMethod(); - // path are needed to compute version - const cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, { - compressionMethod - }); - if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) { - // Cache not found - return undefined; - } - const archivePath = path.join(yield utils.createTempDirectory(), utils.getCacheFileName(compressionMethod)); - core.debug(`Archive Path: ${archivePath}`); + let archivePath = ''; try { + // path are needed to compute version + const cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, { + compressionMethod + }); + if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) { + // Cache not found + return undefined; + } + archivePath = path.join(yield utils.createTempDirectory(), utils.getCacheFileName(compressionMethod)); + core.debug(`Archive Path: ${archivePath}`); // Download the cache from the cache entry yield cacheHttpClient.downloadCache(cacheEntry.archiveLocation, archivePath, options); if (core.isDebug()) { @@ -110,6 +111,17 @@ function restoreCache(paths, primaryKey, restoreKeys, options) { core.info(`Cache Size: ~${Math.round(archiveFileSize / (1024 * 1024))} MB (${archiveFileSize} B)`); yield tar_1.extractTar(archivePath, compressionMethod); core.info('Cache restored successfully'); + return cacheEntry.cacheKey; + } + catch (error) { + const typedError = error; + if (typedError.name === ValidationError.name) { + throw error; + } + else { + // Supress all non-validation cache related errors because caching should be optional + core.warning(`Failed to restore: ${error.message}`); + } } finally { // Try to delete the archive to save space @@ -120,7 +132,7 @@ function restoreCache(paths, primaryKey, restoreKeys, options) { core.debug(`Failed to delete archive: ${error}`); } } - return cacheEntry.cacheKey; + return undefined; }); } exports.restoreCache = restoreCache; @@ -138,10 +150,13 @@ function saveCache(paths, key, options) { checkPaths(paths); checkKey(key); const compressionMethod = yield utils.getCompressionMethod(); - let cacheId = null; + let cacheId = -1; const cachePaths = yield utils.resolvePaths(paths); core.debug('Cache Paths:'); core.debug(`${JSON.stringify(cachePaths)}`); + if (cachePaths.length === 0) { + throw new Error(`Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.`); + } const archiveFolder = yield utils.createTempDirectory(); const archivePath = path.join(archiveFolder, utils.getCacheFileName(compressionMethod)); core.debug(`Archive Path: ${archivePath}`); @@ -174,6 +189,18 @@ function saveCache(paths, key, options) { core.debug(`Saving Cache (ID: ${cacheId})`); yield cacheHttpClient.saveCache(cacheId, archivePath, options); } + catch (error) { + const typedError = error; + if (typedError.name === ValidationError.name) { + throw error; + } + else if (typedError.name === ReserveCacheError.name) { + core.info(`Failed to save: ${typedError.message}`); + } + else { + core.warning(`Failed to save: ${typedError.message}`); + } + } finally { // Try to delete the archive to save space try { @@ -214,8 +241,8 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); -const http_client_1 = __nccwpck_require__(9925); -const auth_1 = __nccwpck_require__(3702); +const http_client_1 = __nccwpck_require__(1825); +const auth_1 = __nccwpck_require__(2001); const crypto = __importStar(__nccwpck_require__(6113)); const fs = __importStar(__nccwpck_require__(7147)); const url_1 = __nccwpck_require__(7310); @@ -647,7 +674,7 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); -const http_client_1 = __nccwpck_require__(9925); +const http_client_1 = __nccwpck_require__(1825); const storage_blob_1 = __nccwpck_require__(4100); const buffer = __importStar(__nccwpck_require__(4300)); const fs = __importStar(__nccwpck_require__(7147)); @@ -885,7 +912,7 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); -const http_client_1 = __nccwpck_require__(9925); +const http_client_1 = __nccwpck_require__(1825); const constants_1 = __nccwpck_require__(8840); function isSuccessStatusCode(statusCode) { if (!statusCode) { @@ -962,7 +989,7 @@ function retryTypedResponse(name, method, maxAttempts = constants_1.DefaultRetry return __awaiter(this, void 0, void 0, function* () { return yield retry(name, method, (response) => response.statusCode, maxAttempts, delay, // If the error object contains the statusCode property, extract it and return - // an ITypedResponse so it can be processed by the retry logic. + // an TypedResponse so it can be processed by the retry logic. (error) => { if (error instanceof http_client_1.HttpClientError) { return { @@ -1120,6 +1147,8 @@ function createTar(archiveFolder, sourceDirectories, compressionMethod) { ...getCompressionProgram(), '-cf', cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), + '--exclude', + cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '-P', '-C', workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), @@ -2292,6 +2321,774 @@ exports.SearchState = SearchState; /***/ }), +/***/ 2001: +/***/ (function(__unused_webpack_module, exports) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.PersonalAccessTokenCredentialHandler = exports.BearerCredentialHandler = exports.BasicCredentialHandler = void 0; +class BasicCredentialHandler { + constructor(username, password) { + this.username = username; + this.password = password; + } + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.BasicCredentialHandler = BasicCredentialHandler; +class BearerCredentialHandler { + constructor(token) { + this.token = token; + } + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Bearer ${this.token}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.BearerCredentialHandler = BearerCredentialHandler; +class PersonalAccessTokenCredentialHandler { + constructor(token) { + this.token = token; + } + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler; +//# sourceMappingURL=auth.js.map + +/***/ }), + +/***/ 1825: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.HttpClient = exports.isHttps = exports.HttpClientResponse = exports.HttpClientError = exports.getProxyUrl = exports.MediaTypes = exports.Headers = exports.HttpCodes = void 0; +const http = __importStar(__nccwpck_require__(3685)); +const https = __importStar(__nccwpck_require__(5687)); +const pm = __importStar(__nccwpck_require__(4977)); +const tunnel = __importStar(__nccwpck_require__(4294)); +var HttpCodes; +(function (HttpCodes) { + HttpCodes[HttpCodes["OK"] = 200] = "OK"; + HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices"; + HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently"; + HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved"; + HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther"; + HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified"; + HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy"; + HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy"; + HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect"; + HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect"; + HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest"; + HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized"; + HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired"; + HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden"; + HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound"; + HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed"; + HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable"; + HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired"; + HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout"; + HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict"; + HttpCodes[HttpCodes["Gone"] = 410] = "Gone"; + HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests"; + HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError"; + HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented"; + HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway"; + HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable"; + HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout"; +})(HttpCodes = exports.HttpCodes || (exports.HttpCodes = {})); +var Headers; +(function (Headers) { + Headers["Accept"] = "accept"; + Headers["ContentType"] = "content-type"; +})(Headers = exports.Headers || (exports.Headers = {})); +var MediaTypes; +(function (MediaTypes) { + MediaTypes["ApplicationJson"] = "application/json"; +})(MediaTypes = exports.MediaTypes || (exports.MediaTypes = {})); +/** + * Returns the proxy URL, depending upon the supplied url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ +function getProxyUrl(serverUrl) { + const proxyUrl = pm.getProxyUrl(new URL(serverUrl)); + return proxyUrl ? proxyUrl.href : ''; +} +exports.getProxyUrl = getProxyUrl; +const HttpRedirectCodes = [ + HttpCodes.MovedPermanently, + HttpCodes.ResourceMoved, + HttpCodes.SeeOther, + HttpCodes.TemporaryRedirect, + HttpCodes.PermanentRedirect +]; +const HttpResponseRetryCodes = [ + HttpCodes.BadGateway, + HttpCodes.ServiceUnavailable, + HttpCodes.GatewayTimeout +]; +const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD']; +const ExponentialBackoffCeiling = 10; +const ExponentialBackoffTimeSlice = 5; +class HttpClientError extends Error { + constructor(message, statusCode) { + super(message); + this.name = 'HttpClientError'; + this.statusCode = statusCode; + Object.setPrototypeOf(this, HttpClientError.prototype); + } +} +exports.HttpClientError = HttpClientError; +class HttpClientResponse { + constructor(message) { + this.message = message; + } + readBody() { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { + let output = Buffer.alloc(0); + this.message.on('data', (chunk) => { + output = Buffer.concat([output, chunk]); + }); + this.message.on('end', () => { + resolve(output.toString()); + }); + })); + }); + } +} +exports.HttpClientResponse = HttpClientResponse; +function isHttps(requestUrl) { + const parsedUrl = new URL(requestUrl); + return parsedUrl.protocol === 'https:'; +} +exports.isHttps = isHttps; +class HttpClient { + constructor(userAgent, handlers, requestOptions) { + this._ignoreSslError = false; + this._allowRedirects = true; + this._allowRedirectDowngrade = false; + this._maxRedirects = 50; + this._allowRetries = false; + this._maxRetries = 1; + this._keepAlive = false; + this._disposed = false; + this.userAgent = userAgent; + this.handlers = handlers || []; + this.requestOptions = requestOptions; + if (requestOptions) { + if (requestOptions.ignoreSslError != null) { + this._ignoreSslError = requestOptions.ignoreSslError; + } + this._socketTimeout = requestOptions.socketTimeout; + if (requestOptions.allowRedirects != null) { + this._allowRedirects = requestOptions.allowRedirects; + } + if (requestOptions.allowRedirectDowngrade != null) { + this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade; + } + if (requestOptions.maxRedirects != null) { + this._maxRedirects = Math.max(requestOptions.maxRedirects, 0); + } + if (requestOptions.keepAlive != null) { + this._keepAlive = requestOptions.keepAlive; + } + if (requestOptions.allowRetries != null) { + this._allowRetries = requestOptions.allowRetries; + } + if (requestOptions.maxRetries != null) { + this._maxRetries = requestOptions.maxRetries; + } + } + } + options(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}); + }); + } + get(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('GET', requestUrl, null, additionalHeaders || {}); + }); + } + del(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('DELETE', requestUrl, null, additionalHeaders || {}); + }); + } + post(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('POST', requestUrl, data, additionalHeaders || {}); + }); + } + patch(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PATCH', requestUrl, data, additionalHeaders || {}); + }); + } + put(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PUT', requestUrl, data, additionalHeaders || {}); + }); + } + head(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('HEAD', requestUrl, null, additionalHeaders || {}); + }); + } + sendStream(verb, requestUrl, stream, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request(verb, requestUrl, stream, additionalHeaders); + }); + } + /** + * Gets a typed object from an endpoint + * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise + */ + getJson(requestUrl, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + const res = yield this.get(requestUrl, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + postJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.post(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + putJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.put(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + patchJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.patch(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + /** + * Makes a raw http request. + * All other methods such as get, post, patch, and request ultimately call this. + * Prefer get, del, post and patch + */ + request(verb, requestUrl, data, headers) { + return __awaiter(this, void 0, void 0, function* () { + if (this._disposed) { + throw new Error('Client has already been disposed.'); + } + const parsedUrl = new URL(requestUrl); + let info = this._prepareRequest(verb, parsedUrl, headers); + // Only perform retries on reads since writes may not be idempotent. + const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) + ? this._maxRetries + 1 + : 1; + let numTries = 0; + let response; + do { + response = yield this.requestRaw(info, data); + // Check if it's an authentication challenge + if (response && + response.message && + response.message.statusCode === HttpCodes.Unauthorized) { + let authenticationHandler; + for (const handler of this.handlers) { + if (handler.canHandleAuthentication(response)) { + authenticationHandler = handler; + break; + } + } + if (authenticationHandler) { + return authenticationHandler.handleAuthentication(this, info, data); + } + else { + // We have received an unauthorized response but have no handlers to handle it. + // Let the response return to the caller. + return response; + } + } + let redirectsRemaining = this._maxRedirects; + while (response.message.statusCode && + HttpRedirectCodes.includes(response.message.statusCode) && + this._allowRedirects && + redirectsRemaining > 0) { + const redirectUrl = response.message.headers['location']; + if (!redirectUrl) { + // if there's no location to redirect to, we won't + break; + } + const parsedRedirectUrl = new URL(redirectUrl); + if (parsedUrl.protocol === 'https:' && + parsedUrl.protocol !== parsedRedirectUrl.protocol && + !this._allowRedirectDowngrade) { + throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.'); + } + // we need to finish reading the response before reassigning response + // which will leak the open socket. + yield response.readBody(); + // strip authorization header if redirected to a different hostname + if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { + for (const header in headers) { + // header names are case insensitive + if (header.toLowerCase() === 'authorization') { + delete headers[header]; + } + } + } + // let's make the request with the new redirectUrl + info = this._prepareRequest(verb, parsedRedirectUrl, headers); + response = yield this.requestRaw(info, data); + redirectsRemaining--; + } + if (!response.message.statusCode || + !HttpResponseRetryCodes.includes(response.message.statusCode)) { + // If not a retry code, return immediately instead of retrying + return response; + } + numTries += 1; + if (numTries < maxTries) { + yield response.readBody(); + yield this._performExponentialBackoff(numTries); + } + } while (numTries < maxTries); + return response; + }); + } + /** + * Needs to be called if keepAlive is set to true in request options. + */ + dispose() { + if (this._agent) { + this._agent.destroy(); + } + this._disposed = true; + } + /** + * Raw request. + * @param info + * @param data + */ + requestRaw(info, data) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => { + function callbackForResult(err, res) { + if (err) { + reject(err); + } + else if (!res) { + // If `err` is not passed, then `res` must be passed. + reject(new Error('Unknown error')); + } + else { + resolve(res); + } + } + this.requestRawWithCallback(info, data, callbackForResult); + }); + }); + } + /** + * Raw request with callback. + * @param info + * @param data + * @param onResult + */ + requestRawWithCallback(info, data, onResult) { + if (typeof data === 'string') { + if (!info.options.headers) { + info.options.headers = {}; + } + info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); + } + let callbackCalled = false; + function handleResult(err, res) { + if (!callbackCalled) { + callbackCalled = true; + onResult(err, res); + } + } + const req = info.httpModule.request(info.options, (msg) => { + const res = new HttpClientResponse(msg); + handleResult(undefined, res); + }); + let socket; + req.on('socket', sock => { + socket = sock; + }); + // If we ever get disconnected, we want the socket to timeout eventually + req.setTimeout(this._socketTimeout || 3 * 60000, () => { + if (socket) { + socket.end(); + } + handleResult(new Error(`Request timeout: ${info.options.path}`)); + }); + req.on('error', function (err) { + // err has statusCode property + // res should have headers + handleResult(err); + }); + if (data && typeof data === 'string') { + req.write(data, 'utf8'); + } + if (data && typeof data !== 'string') { + data.on('close', function () { + req.end(); + }); + data.pipe(req); + } + else { + req.end(); + } + } + /** + * Gets an http agent. This function is useful when you need an http agent that handles + * routing through a proxy server - depending upon the url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ + getAgent(serverUrl) { + const parsedUrl = new URL(serverUrl); + return this._getAgent(parsedUrl); + } + _prepareRequest(method, requestUrl, headers) { + const info = {}; + info.parsedUrl = requestUrl; + const usingSsl = info.parsedUrl.protocol === 'https:'; + info.httpModule = usingSsl ? https : http; + const defaultPort = usingSsl ? 443 : 80; + info.options = {}; + info.options.host = info.parsedUrl.hostname; + info.options.port = info.parsedUrl.port + ? parseInt(info.parsedUrl.port) + : defaultPort; + info.options.path = + (info.parsedUrl.pathname || '') + (info.parsedUrl.search || ''); + info.options.method = method; + info.options.headers = this._mergeHeaders(headers); + if (this.userAgent != null) { + info.options.headers['user-agent'] = this.userAgent; + } + info.options.agent = this._getAgent(info.parsedUrl); + // gives handlers an opportunity to participate + if (this.handlers) { + for (const handler of this.handlers) { + handler.prepareRequest(info.options); + } + } + return info; + } + _mergeHeaders(headers) { + if (this.requestOptions && this.requestOptions.headers) { + return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers || {})); + } + return lowercaseKeys(headers || {}); + } + _getExistingOrDefaultHeader(additionalHeaders, header, _default) { + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + clientHeader = lowercaseKeys(this.requestOptions.headers)[header]; + } + return additionalHeaders[header] || clientHeader || _default; + } + _getAgent(parsedUrl) { + let agent; + const proxyUrl = pm.getProxyUrl(parsedUrl); + const useProxy = proxyUrl && proxyUrl.hostname; + if (this._keepAlive && useProxy) { + agent = this._proxyAgent; + } + if (this._keepAlive && !useProxy) { + agent = this._agent; + } + // if agent is already assigned use that agent. + if (agent) { + return agent; + } + const usingSsl = parsedUrl.protocol === 'https:'; + let maxSockets = 100; + if (this.requestOptions) { + maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets; + } + // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis. + if (proxyUrl && proxyUrl.hostname) { + const agentOptions = { + maxSockets, + keepAlive: this._keepAlive, + proxy: Object.assign(Object.assign({}, ((proxyUrl.username || proxyUrl.password) && { + proxyAuth: `${proxyUrl.username}:${proxyUrl.password}` + })), { host: proxyUrl.hostname, port: proxyUrl.port }) + }; + let tunnelAgent; + const overHttps = proxyUrl.protocol === 'https:'; + if (usingSsl) { + tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp; + } + else { + tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp; + } + agent = tunnelAgent(agentOptions); + this._proxyAgent = agent; + } + // if reusing agent across request and tunneling agent isn't assigned create a new agent + if (this._keepAlive && !agent) { + const options = { keepAlive: this._keepAlive, maxSockets }; + agent = usingSsl ? new https.Agent(options) : new http.Agent(options); + this._agent = agent; + } + // if not using private agent and tunnel agent isn't setup then use global agent + if (!agent) { + agent = usingSsl ? https.globalAgent : http.globalAgent; + } + if (usingSsl && this._ignoreSslError) { + // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process + // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options + // we have to cast it to any and change it directly + agent.options = Object.assign(agent.options || {}, { + rejectUnauthorized: false + }); + } + return agent; + } + _performExponentialBackoff(retryNumber) { + return __awaiter(this, void 0, void 0, function* () { + retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber); + const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber); + return new Promise(resolve => setTimeout(() => resolve(), ms)); + }); + } + _processResponse(res, options) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { + const statusCode = res.message.statusCode || 0; + const response = { + statusCode, + result: null, + headers: {} + }; + // not found leads to null obj returned + if (statusCode === HttpCodes.NotFound) { + resolve(response); + } + // get the result from the body + function dateTimeDeserializer(key, value) { + if (typeof value === 'string') { + const a = new Date(value); + if (!isNaN(a.valueOf())) { + return a; + } + } + return value; + } + let obj; + let contents; + try { + contents = yield res.readBody(); + if (contents && contents.length > 0) { + if (options && options.deserializeDates) { + obj = JSON.parse(contents, dateTimeDeserializer); + } + else { + obj = JSON.parse(contents); + } + response.result = obj; + } + response.headers = res.message.headers; + } + catch (err) { + // Invalid resource (contents not json); leaving result obj null + } + // note that 3xx redirects are handled by the http layer. + if (statusCode > 299) { + let msg; + // if exception/error in body, attempt to get better error + if (obj && obj.message) { + msg = obj.message; + } + else if (contents && contents.length > 0) { + // it may be the case that the exception is in the body message as string + msg = contents; + } + else { + msg = `Failed request: (${statusCode})`; + } + const err = new HttpClientError(msg, statusCode); + err.result = response.result; + reject(err); + } + else { + resolve(response); + } + })); + }); + } +} +exports.HttpClient = HttpClient; +const lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}); +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 4977: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.checkBypass = exports.getProxyUrl = void 0; +function getProxyUrl(reqUrl) { + const usingSsl = reqUrl.protocol === 'https:'; + if (checkBypass(reqUrl)) { + return undefined; + } + const proxyVar = (() => { + if (usingSsl) { + return process.env['https_proxy'] || process.env['HTTPS_PROXY']; + } + else { + return process.env['http_proxy'] || process.env['HTTP_PROXY']; + } + })(); + if (proxyVar) { + return new URL(proxyVar); + } + else { + return undefined; + } +} +exports.getProxyUrl = getProxyUrl; +function checkBypass(reqUrl) { + if (!reqUrl.hostname) { + return false; + } + const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''; + if (!noProxy) { + return false; + } + // Determine the request port + let reqPort; + if (reqUrl.port) { + reqPort = Number(reqUrl.port); + } + else if (reqUrl.protocol === 'http:') { + reqPort = 80; + } + else if (reqUrl.protocol === 'https:') { + reqPort = 443; + } + // Format the request hostname and hostname with port + const upperReqHosts = [reqUrl.hostname.toUpperCase()]; + if (typeof reqPort === 'number') { + upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`); + } + // Compare request host against noproxy + for (const upperNoProxyItem of noProxy + .split(',') + .map(x => x.trim().toUpperCase()) + .filter(x => x)) { + if (upperReqHosts.some(x => x === upperNoProxyItem)) { + return true; + } + } + return false; +} +exports.checkBypass = checkBypass; +//# sourceMappingURL=proxy.js.map + +/***/ }), + /***/ 3771: /***/ ((module, exports) => { @@ -58120,19 +58917,11 @@ function saveCache(packageManager) { core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`); return; } - try { - yield cache.saveCache(cachePaths, primaryKey); - core.info(`Cache saved with the key: ${primaryKey}`); - } - catch (error) { - const err = error; - if (err.name === cache.ReserveCacheError.name) { - core.info(err.message); - } - else { - throw error; - } + const cacheId = yield cache.saveCache(cachePaths, primaryKey); + if (cacheId == -1) { + return; } + core.info(`Cache saved with the key: ${primaryKey}`); }); } function isCacheDirectoryExists(cacheDirectory) { diff --git a/dist/setup/index.js b/dist/setup/index.js index 113174d..9ac89a4 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -90,17 +90,18 @@ function restoreCache(paths, primaryKey, restoreKeys, options) { checkKey(key); } const compressionMethod = yield utils.getCompressionMethod(); - // path are needed to compute version - const cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, { - compressionMethod - }); - if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) { - // Cache not found - return undefined; - } - const archivePath = path.join(yield utils.createTempDirectory(), utils.getCacheFileName(compressionMethod)); - core.debug(`Archive Path: ${archivePath}`); + let archivePath = ''; try { + // path are needed to compute version + const cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, { + compressionMethod + }); + if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) { + // Cache not found + return undefined; + } + archivePath = path.join(yield utils.createTempDirectory(), utils.getCacheFileName(compressionMethod)); + core.debug(`Archive Path: ${archivePath}`); // Download the cache from the cache entry yield cacheHttpClient.downloadCache(cacheEntry.archiveLocation, archivePath, options); if (core.isDebug()) { @@ -110,6 +111,17 @@ function restoreCache(paths, primaryKey, restoreKeys, options) { core.info(`Cache Size: ~${Math.round(archiveFileSize / (1024 * 1024))} MB (${archiveFileSize} B)`); yield tar_1.extractTar(archivePath, compressionMethod); core.info('Cache restored successfully'); + return cacheEntry.cacheKey; + } + catch (error) { + const typedError = error; + if (typedError.name === ValidationError.name) { + throw error; + } + else { + // Supress all non-validation cache related errors because caching should be optional + core.warning(`Failed to restore: ${error.message}`); + } } finally { // Try to delete the archive to save space @@ -120,7 +132,7 @@ function restoreCache(paths, primaryKey, restoreKeys, options) { core.debug(`Failed to delete archive: ${error}`); } } - return cacheEntry.cacheKey; + return undefined; }); } exports.restoreCache = restoreCache; @@ -138,10 +150,13 @@ function saveCache(paths, key, options) { checkPaths(paths); checkKey(key); const compressionMethod = yield utils.getCompressionMethod(); - let cacheId = null; + let cacheId = -1; const cachePaths = yield utils.resolvePaths(paths); core.debug('Cache Paths:'); core.debug(`${JSON.stringify(cachePaths)}`); + if (cachePaths.length === 0) { + throw new Error(`Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.`); + } const archiveFolder = yield utils.createTempDirectory(); const archivePath = path.join(archiveFolder, utils.getCacheFileName(compressionMethod)); core.debug(`Archive Path: ${archivePath}`); @@ -174,6 +189,18 @@ function saveCache(paths, key, options) { core.debug(`Saving Cache (ID: ${cacheId})`); yield cacheHttpClient.saveCache(cacheId, archivePath, options); } + catch (error) { + const typedError = error; + if (typedError.name === ValidationError.name) { + throw error; + } + else if (typedError.name === ReserveCacheError.name) { + core.info(`Failed to save: ${typedError.message}`); + } + else { + core.warning(`Failed to save: ${typedError.message}`); + } + } finally { // Try to delete the archive to save space try { @@ -214,8 +241,8 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); -const http_client_1 = __nccwpck_require__(9925); -const auth_1 = __nccwpck_require__(3702); +const http_client_1 = __nccwpck_require__(1825); +const auth_1 = __nccwpck_require__(2001); const crypto = __importStar(__nccwpck_require__(6113)); const fs = __importStar(__nccwpck_require__(7147)); const url_1 = __nccwpck_require__(7310); @@ -647,7 +674,7 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); -const http_client_1 = __nccwpck_require__(9925); +const http_client_1 = __nccwpck_require__(1825); const storage_blob_1 = __nccwpck_require__(4100); const buffer = __importStar(__nccwpck_require__(4300)); const fs = __importStar(__nccwpck_require__(7147)); @@ -885,7 +912,7 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); -const http_client_1 = __nccwpck_require__(9925); +const http_client_1 = __nccwpck_require__(1825); const constants_1 = __nccwpck_require__(8840); function isSuccessStatusCode(statusCode) { if (!statusCode) { @@ -962,7 +989,7 @@ function retryTypedResponse(name, method, maxAttempts = constants_1.DefaultRetry return __awaiter(this, void 0, void 0, function* () { return yield retry(name, method, (response) => response.statusCode, maxAttempts, delay, // If the error object contains the statusCode property, extract it and return - // an ITypedResponse so it can be processed by the retry logic. + // an TypedResponse so it can be processed by the retry logic. (error) => { if (error instanceof http_client_1.HttpClientError) { return { @@ -1120,6 +1147,8 @@ function createTar(archiveFolder, sourceDirectories, compressionMethod) { ...getCompressionProgram(), '-cf', cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), + '--exclude', + cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '-P', '-C', workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), @@ -2292,6 +2321,774 @@ exports.SearchState = SearchState; /***/ }), +/***/ 2001: +/***/ (function(__unused_webpack_module, exports) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.PersonalAccessTokenCredentialHandler = exports.BearerCredentialHandler = exports.BasicCredentialHandler = void 0; +class BasicCredentialHandler { + constructor(username, password) { + this.username = username; + this.password = password; + } + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.BasicCredentialHandler = BasicCredentialHandler; +class BearerCredentialHandler { + constructor(token) { + this.token = token; + } + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Bearer ${this.token}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.BearerCredentialHandler = BearerCredentialHandler; +class PersonalAccessTokenCredentialHandler { + constructor(token) { + this.token = token; + } + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler; +//# sourceMappingURL=auth.js.map + +/***/ }), + +/***/ 1825: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.HttpClient = exports.isHttps = exports.HttpClientResponse = exports.HttpClientError = exports.getProxyUrl = exports.MediaTypes = exports.Headers = exports.HttpCodes = void 0; +const http = __importStar(__nccwpck_require__(3685)); +const https = __importStar(__nccwpck_require__(5687)); +const pm = __importStar(__nccwpck_require__(4977)); +const tunnel = __importStar(__nccwpck_require__(4294)); +var HttpCodes; +(function (HttpCodes) { + HttpCodes[HttpCodes["OK"] = 200] = "OK"; + HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices"; + HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently"; + HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved"; + HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther"; + HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified"; + HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy"; + HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy"; + HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect"; + HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect"; + HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest"; + HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized"; + HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired"; + HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden"; + HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound"; + HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed"; + HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable"; + HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired"; + HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout"; + HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict"; + HttpCodes[HttpCodes["Gone"] = 410] = "Gone"; + HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests"; + HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError"; + HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented"; + HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway"; + HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable"; + HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout"; +})(HttpCodes = exports.HttpCodes || (exports.HttpCodes = {})); +var Headers; +(function (Headers) { + Headers["Accept"] = "accept"; + Headers["ContentType"] = "content-type"; +})(Headers = exports.Headers || (exports.Headers = {})); +var MediaTypes; +(function (MediaTypes) { + MediaTypes["ApplicationJson"] = "application/json"; +})(MediaTypes = exports.MediaTypes || (exports.MediaTypes = {})); +/** + * Returns the proxy URL, depending upon the supplied url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ +function getProxyUrl(serverUrl) { + const proxyUrl = pm.getProxyUrl(new URL(serverUrl)); + return proxyUrl ? proxyUrl.href : ''; +} +exports.getProxyUrl = getProxyUrl; +const HttpRedirectCodes = [ + HttpCodes.MovedPermanently, + HttpCodes.ResourceMoved, + HttpCodes.SeeOther, + HttpCodes.TemporaryRedirect, + HttpCodes.PermanentRedirect +]; +const HttpResponseRetryCodes = [ + HttpCodes.BadGateway, + HttpCodes.ServiceUnavailable, + HttpCodes.GatewayTimeout +]; +const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD']; +const ExponentialBackoffCeiling = 10; +const ExponentialBackoffTimeSlice = 5; +class HttpClientError extends Error { + constructor(message, statusCode) { + super(message); + this.name = 'HttpClientError'; + this.statusCode = statusCode; + Object.setPrototypeOf(this, HttpClientError.prototype); + } +} +exports.HttpClientError = HttpClientError; +class HttpClientResponse { + constructor(message) { + this.message = message; + } + readBody() { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { + let output = Buffer.alloc(0); + this.message.on('data', (chunk) => { + output = Buffer.concat([output, chunk]); + }); + this.message.on('end', () => { + resolve(output.toString()); + }); + })); + }); + } +} +exports.HttpClientResponse = HttpClientResponse; +function isHttps(requestUrl) { + const parsedUrl = new URL(requestUrl); + return parsedUrl.protocol === 'https:'; +} +exports.isHttps = isHttps; +class HttpClient { + constructor(userAgent, handlers, requestOptions) { + this._ignoreSslError = false; + this._allowRedirects = true; + this._allowRedirectDowngrade = false; + this._maxRedirects = 50; + this._allowRetries = false; + this._maxRetries = 1; + this._keepAlive = false; + this._disposed = false; + this.userAgent = userAgent; + this.handlers = handlers || []; + this.requestOptions = requestOptions; + if (requestOptions) { + if (requestOptions.ignoreSslError != null) { + this._ignoreSslError = requestOptions.ignoreSslError; + } + this._socketTimeout = requestOptions.socketTimeout; + if (requestOptions.allowRedirects != null) { + this._allowRedirects = requestOptions.allowRedirects; + } + if (requestOptions.allowRedirectDowngrade != null) { + this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade; + } + if (requestOptions.maxRedirects != null) { + this._maxRedirects = Math.max(requestOptions.maxRedirects, 0); + } + if (requestOptions.keepAlive != null) { + this._keepAlive = requestOptions.keepAlive; + } + if (requestOptions.allowRetries != null) { + this._allowRetries = requestOptions.allowRetries; + } + if (requestOptions.maxRetries != null) { + this._maxRetries = requestOptions.maxRetries; + } + } + } + options(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}); + }); + } + get(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('GET', requestUrl, null, additionalHeaders || {}); + }); + } + del(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('DELETE', requestUrl, null, additionalHeaders || {}); + }); + } + post(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('POST', requestUrl, data, additionalHeaders || {}); + }); + } + patch(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PATCH', requestUrl, data, additionalHeaders || {}); + }); + } + put(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PUT', requestUrl, data, additionalHeaders || {}); + }); + } + head(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('HEAD', requestUrl, null, additionalHeaders || {}); + }); + } + sendStream(verb, requestUrl, stream, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request(verb, requestUrl, stream, additionalHeaders); + }); + } + /** + * Gets a typed object from an endpoint + * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise + */ + getJson(requestUrl, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + const res = yield this.get(requestUrl, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + postJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.post(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + putJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.put(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + patchJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.patch(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + /** + * Makes a raw http request. + * All other methods such as get, post, patch, and request ultimately call this. + * Prefer get, del, post and patch + */ + request(verb, requestUrl, data, headers) { + return __awaiter(this, void 0, void 0, function* () { + if (this._disposed) { + throw new Error('Client has already been disposed.'); + } + const parsedUrl = new URL(requestUrl); + let info = this._prepareRequest(verb, parsedUrl, headers); + // Only perform retries on reads since writes may not be idempotent. + const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) + ? this._maxRetries + 1 + : 1; + let numTries = 0; + let response; + do { + response = yield this.requestRaw(info, data); + // Check if it's an authentication challenge + if (response && + response.message && + response.message.statusCode === HttpCodes.Unauthorized) { + let authenticationHandler; + for (const handler of this.handlers) { + if (handler.canHandleAuthentication(response)) { + authenticationHandler = handler; + break; + } + } + if (authenticationHandler) { + return authenticationHandler.handleAuthentication(this, info, data); + } + else { + // We have received an unauthorized response but have no handlers to handle it. + // Let the response return to the caller. + return response; + } + } + let redirectsRemaining = this._maxRedirects; + while (response.message.statusCode && + HttpRedirectCodes.includes(response.message.statusCode) && + this._allowRedirects && + redirectsRemaining > 0) { + const redirectUrl = response.message.headers['location']; + if (!redirectUrl) { + // if there's no location to redirect to, we won't + break; + } + const parsedRedirectUrl = new URL(redirectUrl); + if (parsedUrl.protocol === 'https:' && + parsedUrl.protocol !== parsedRedirectUrl.protocol && + !this._allowRedirectDowngrade) { + throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.'); + } + // we need to finish reading the response before reassigning response + // which will leak the open socket. + yield response.readBody(); + // strip authorization header if redirected to a different hostname + if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { + for (const header in headers) { + // header names are case insensitive + if (header.toLowerCase() === 'authorization') { + delete headers[header]; + } + } + } + // let's make the request with the new redirectUrl + info = this._prepareRequest(verb, parsedRedirectUrl, headers); + response = yield this.requestRaw(info, data); + redirectsRemaining--; + } + if (!response.message.statusCode || + !HttpResponseRetryCodes.includes(response.message.statusCode)) { + // If not a retry code, return immediately instead of retrying + return response; + } + numTries += 1; + if (numTries < maxTries) { + yield response.readBody(); + yield this._performExponentialBackoff(numTries); + } + } while (numTries < maxTries); + return response; + }); + } + /** + * Needs to be called if keepAlive is set to true in request options. + */ + dispose() { + if (this._agent) { + this._agent.destroy(); + } + this._disposed = true; + } + /** + * Raw request. + * @param info + * @param data + */ + requestRaw(info, data) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => { + function callbackForResult(err, res) { + if (err) { + reject(err); + } + else if (!res) { + // If `err` is not passed, then `res` must be passed. + reject(new Error('Unknown error')); + } + else { + resolve(res); + } + } + this.requestRawWithCallback(info, data, callbackForResult); + }); + }); + } + /** + * Raw request with callback. + * @param info + * @param data + * @param onResult + */ + requestRawWithCallback(info, data, onResult) { + if (typeof data === 'string') { + if (!info.options.headers) { + info.options.headers = {}; + } + info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); + } + let callbackCalled = false; + function handleResult(err, res) { + if (!callbackCalled) { + callbackCalled = true; + onResult(err, res); + } + } + const req = info.httpModule.request(info.options, (msg) => { + const res = new HttpClientResponse(msg); + handleResult(undefined, res); + }); + let socket; + req.on('socket', sock => { + socket = sock; + }); + // If we ever get disconnected, we want the socket to timeout eventually + req.setTimeout(this._socketTimeout || 3 * 60000, () => { + if (socket) { + socket.end(); + } + handleResult(new Error(`Request timeout: ${info.options.path}`)); + }); + req.on('error', function (err) { + // err has statusCode property + // res should have headers + handleResult(err); + }); + if (data && typeof data === 'string') { + req.write(data, 'utf8'); + } + if (data && typeof data !== 'string') { + data.on('close', function () { + req.end(); + }); + data.pipe(req); + } + else { + req.end(); + } + } + /** + * Gets an http agent. This function is useful when you need an http agent that handles + * routing through a proxy server - depending upon the url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ + getAgent(serverUrl) { + const parsedUrl = new URL(serverUrl); + return this._getAgent(parsedUrl); + } + _prepareRequest(method, requestUrl, headers) { + const info = {}; + info.parsedUrl = requestUrl; + const usingSsl = info.parsedUrl.protocol === 'https:'; + info.httpModule = usingSsl ? https : http; + const defaultPort = usingSsl ? 443 : 80; + info.options = {}; + info.options.host = info.parsedUrl.hostname; + info.options.port = info.parsedUrl.port + ? parseInt(info.parsedUrl.port) + : defaultPort; + info.options.path = + (info.parsedUrl.pathname || '') + (info.parsedUrl.search || ''); + info.options.method = method; + info.options.headers = this._mergeHeaders(headers); + if (this.userAgent != null) { + info.options.headers['user-agent'] = this.userAgent; + } + info.options.agent = this._getAgent(info.parsedUrl); + // gives handlers an opportunity to participate + if (this.handlers) { + for (const handler of this.handlers) { + handler.prepareRequest(info.options); + } + } + return info; + } + _mergeHeaders(headers) { + if (this.requestOptions && this.requestOptions.headers) { + return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers || {})); + } + return lowercaseKeys(headers || {}); + } + _getExistingOrDefaultHeader(additionalHeaders, header, _default) { + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + clientHeader = lowercaseKeys(this.requestOptions.headers)[header]; + } + return additionalHeaders[header] || clientHeader || _default; + } + _getAgent(parsedUrl) { + let agent; + const proxyUrl = pm.getProxyUrl(parsedUrl); + const useProxy = proxyUrl && proxyUrl.hostname; + if (this._keepAlive && useProxy) { + agent = this._proxyAgent; + } + if (this._keepAlive && !useProxy) { + agent = this._agent; + } + // if agent is already assigned use that agent. + if (agent) { + return agent; + } + const usingSsl = parsedUrl.protocol === 'https:'; + let maxSockets = 100; + if (this.requestOptions) { + maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets; + } + // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis. + if (proxyUrl && proxyUrl.hostname) { + const agentOptions = { + maxSockets, + keepAlive: this._keepAlive, + proxy: Object.assign(Object.assign({}, ((proxyUrl.username || proxyUrl.password) && { + proxyAuth: `${proxyUrl.username}:${proxyUrl.password}` + })), { host: proxyUrl.hostname, port: proxyUrl.port }) + }; + let tunnelAgent; + const overHttps = proxyUrl.protocol === 'https:'; + if (usingSsl) { + tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp; + } + else { + tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp; + } + agent = tunnelAgent(agentOptions); + this._proxyAgent = agent; + } + // if reusing agent across request and tunneling agent isn't assigned create a new agent + if (this._keepAlive && !agent) { + const options = { keepAlive: this._keepAlive, maxSockets }; + agent = usingSsl ? new https.Agent(options) : new http.Agent(options); + this._agent = agent; + } + // if not using private agent and tunnel agent isn't setup then use global agent + if (!agent) { + agent = usingSsl ? https.globalAgent : http.globalAgent; + } + if (usingSsl && this._ignoreSslError) { + // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process + // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options + // we have to cast it to any and change it directly + agent.options = Object.assign(agent.options || {}, { + rejectUnauthorized: false + }); + } + return agent; + } + _performExponentialBackoff(retryNumber) { + return __awaiter(this, void 0, void 0, function* () { + retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber); + const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber); + return new Promise(resolve => setTimeout(() => resolve(), ms)); + }); + } + _processResponse(res, options) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { + const statusCode = res.message.statusCode || 0; + const response = { + statusCode, + result: null, + headers: {} + }; + // not found leads to null obj returned + if (statusCode === HttpCodes.NotFound) { + resolve(response); + } + // get the result from the body + function dateTimeDeserializer(key, value) { + if (typeof value === 'string') { + const a = new Date(value); + if (!isNaN(a.valueOf())) { + return a; + } + } + return value; + } + let obj; + let contents; + try { + contents = yield res.readBody(); + if (contents && contents.length > 0) { + if (options && options.deserializeDates) { + obj = JSON.parse(contents, dateTimeDeserializer); + } + else { + obj = JSON.parse(contents); + } + response.result = obj; + } + response.headers = res.message.headers; + } + catch (err) { + // Invalid resource (contents not json); leaving result obj null + } + // note that 3xx redirects are handled by the http layer. + if (statusCode > 299) { + let msg; + // if exception/error in body, attempt to get better error + if (obj && obj.message) { + msg = obj.message; + } + else if (contents && contents.length > 0) { + // it may be the case that the exception is in the body message as string + msg = contents; + } + else { + msg = `Failed request: (${statusCode})`; + } + const err = new HttpClientError(msg, statusCode); + err.result = response.result; + reject(err); + } + else { + resolve(response); + } + })); + }); + } +} +exports.HttpClient = HttpClient; +const lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}); +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 4977: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.checkBypass = exports.getProxyUrl = void 0; +function getProxyUrl(reqUrl) { + const usingSsl = reqUrl.protocol === 'https:'; + if (checkBypass(reqUrl)) { + return undefined; + } + const proxyVar = (() => { + if (usingSsl) { + return process.env['https_proxy'] || process.env['HTTPS_PROXY']; + } + else { + return process.env['http_proxy'] || process.env['HTTP_PROXY']; + } + })(); + if (proxyVar) { + return new URL(proxyVar); + } + else { + return undefined; + } +} +exports.getProxyUrl = getProxyUrl; +function checkBypass(reqUrl) { + if (!reqUrl.hostname) { + return false; + } + const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''; + if (!noProxy) { + return false; + } + // Determine the request port + let reqPort; + if (reqUrl.port) { + reqPort = Number(reqUrl.port); + } + else if (reqUrl.protocol === 'http:') { + reqPort = 80; + } + else if (reqUrl.protocol === 'https:') { + reqPort = 443; + } + // Format the request hostname and hostname with port + const upperReqHosts = [reqUrl.hostname.toUpperCase()]; + if (typeof reqPort === 'number') { + upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`); + } + // Compare request host against noproxy + for (const upperNoProxyItem of noProxy + .split(',') + .map(x => x.trim().toUpperCase()) + .filter(x => x)) { + if (upperReqHosts.some(x => x === upperNoProxyItem)) { + return true; + } + } + return false; +} +exports.checkBypass = checkBypass; +//# sourceMappingURL=proxy.js.map + +/***/ }), + /***/ 3771: /***/ ((module, exports) => { @@ -63865,44 +64662,37 @@ const utils_1 = __nccwpck_require__(1314); const semver = __importStar(__nccwpck_require__(1383)); const core = __importStar(__nccwpck_require__(2186)); const tc = __importStar(__nccwpck_require__(7784)); -function findPyPyVersion(versionSpec, architecture, checkLatest) { +function findPyPyVersion(versionSpec, architecture, updateEnvironment) { return __awaiter(this, void 0, void 0, function* () { let resolvedPyPyVersion = ''; let resolvedPythonVersion = ''; let installDir; - let releases; const pypyVersionSpec = parsePyPyVersion(versionSpec); - if (checkLatest) { - releases = yield pypyInstall.getAvailablePyPyVersions(); - if (releases && releases.length > 0) { - const releaseData = pypyInstall.findRelease(releases, pypyVersionSpec.pythonVersion, pypyVersionSpec.pypyVersion, architecture); - if (releaseData) { - core.info(`Resolved as PyPy ${releaseData.resolvedPyPyVersion} with Python (${releaseData.resolvedPythonVersion})`); - pypyVersionSpec.pythonVersion = releaseData.resolvedPythonVersion; - pypyVersionSpec.pypyVersion = releaseData.resolvedPyPyVersion; - } - else { - core.info(`Failed to resolve PyPy ${pypyVersionSpec.pypyVersion} with Python (${pypyVersionSpec.pythonVersion}) from manifest`); - } - } - } ({ installDir, resolvedPythonVersion, resolvedPyPyVersion } = findPyPyToolCache(pypyVersionSpec.pythonVersion, pypyVersionSpec.pypyVersion, architecture)); if (!installDir) { ({ installDir, resolvedPythonVersion, resolvedPyPyVersion - } = yield pypyInstall.installPyPy(pypyVersionSpec.pypyVersion, pypyVersionSpec.pythonVersion, architecture, releases)); + } = yield pypyInstall.installPyPy(pypyVersionSpec.pypyVersion, pypyVersionSpec.pythonVersion, architecture)); } const pipDir = utils_1.IS_WINDOWS ? 'Scripts' : 'bin'; const _binDir = path.join(installDir, pipDir); const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : ''; const pythonPath = path.join(utils_1.IS_WINDOWS ? installDir : _binDir, `python${binaryExtension}`); const pythonLocation = pypyInstall.getPyPyBinaryPath(installDir); - core.exportVariable('pythonLocation', installDir); - core.exportVariable('PKG_CONFIG_PATH', pythonLocation + '/lib/pkgconfig'); - core.addPath(pythonLocation); - core.addPath(_binDir); + if (updateEnvironment) { + core.exportVariable('pythonLocation', installDir); + // https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython + core.exportVariable('Python_ROOT_DIR', installDir); + // https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython2 + core.exportVariable('Python2_ROOT_DIR', installDir); + // https://cmake.org/cmake/help/latest/module/FindPython3.html#module:FindPython3 + core.exportVariable('Python3_ROOT_DIR', installDir); + core.exportVariable('PKG_CONFIG_PATH', pythonLocation + '/lib/pkgconfig'); + core.addPath(pythonLocation); + core.addPath(_binDir); + } core.setOutput('python-version', 'pypy' + resolvedPyPyVersion.trim()); core.setOutput('python-path', pythonPath); return { resolvedPyPyVersion, resolvedPythonVersion }; @@ -64034,28 +64824,15 @@ function binDir(installDir) { return path.join(installDir, 'bin'); } } -function useCpythonVersion(version, architecture, checkLatest) { - var _a; +function useCpythonVersion(version, architecture, updateEnvironment) { return __awaiter(this, void 0, void 0, function* () { - let manifest = null; const desugaredVersionSpec = desugarDevVersion(version); - let semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec); + const semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec); core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`); - if (checkLatest) { - manifest = yield installer.getManifest(); - const resolvedVersion = (_a = (yield installer.findReleaseFromManifest(semanticVersionSpec, architecture, manifest))) === null || _a === void 0 ? void 0 : _a.version; - if (resolvedVersion) { - semanticVersionSpec = resolvedVersion; - core.info(`Resolved as '${semanticVersionSpec}'`); - } - else { - core.info(`Failed to resolve version ${semanticVersionSpec} from manifest`); - } - } let installDir = tc.find('Python', semanticVersionSpec, architecture); if (!installDir) { core.info(`Version ${semanticVersionSpec} was not found in the local cache`); - const foundRelease = yield installer.findReleaseFromManifest(semanticVersionSpec, architecture, manifest); + const foundRelease = yield installer.findReleaseFromManifest(semanticVersionSpec, architecture); if (foundRelease && foundRelease.files && foundRelease.files.length > 0) { core.info(`Version ${semanticVersionSpec} is available for downloading`); yield installer.installCpythonFromRelease(foundRelease); @@ -64068,33 +64845,43 @@ function useCpythonVersion(version, architecture, checkLatest) { `The list of all available versions can be found here: ${installer.MANIFEST_URL}` ].join(os.EOL)); } - core.exportVariable('pythonLocation', installDir); - core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig'); - if (utils_1.IS_LINUX) { - const libPath = process.env.LD_LIBRARY_PATH - ? `:${process.env.LD_LIBRARY_PATH}` - : ''; - const pyLibPath = path.join(installDir, 'lib'); - if (!libPath.split(':').includes(pyLibPath)) { - core.exportVariable('LD_LIBRARY_PATH', pyLibPath + libPath); - } - } const _binDir = binDir(installDir); const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : ''; const pythonPath = path.join(utils_1.IS_WINDOWS ? installDir : _binDir, `python${binaryExtension}`); - core.addPath(installDir); - core.addPath(_binDir); - if (utils_1.IS_WINDOWS) { - // Add --user directory - // `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python//x64/ - // So if `findLocalTool` succeeded above, we must have a conformant `installDir` - const version = path.basename(path.dirname(installDir)); - const major = semver.major(version); - const minor = semver.minor(version); - const userScriptsDir = path.join(process.env['APPDATA'] || '', 'Python', `Python${major}${minor}`, 'Scripts'); - core.addPath(userScriptsDir); + if (updateEnvironment) { + core.exportVariable('pythonLocation', installDir); + core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig'); + core.exportVariable('pythonLocation', installDir); + // https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython + core.exportVariable('Python_ROOT_DIR', installDir); + // https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython2 + core.exportVariable('Python2_ROOT_DIR', installDir); + // https://cmake.org/cmake/help/latest/module/FindPython3.html#module:FindPython3 + core.exportVariable('Python3_ROOT_DIR', installDir); + core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig'); + if (utils_1.IS_LINUX) { + const libPath = process.env.LD_LIBRARY_PATH + ? `:${process.env.LD_LIBRARY_PATH}` + : ''; + const pyLibPath = path.join(installDir, 'lib'); + if (!libPath.split(':').includes(pyLibPath)) { + core.exportVariable('LD_LIBRARY_PATH', pyLibPath + libPath); + } + } + core.addPath(installDir); + core.addPath(_binDir); + if (utils_1.IS_WINDOWS) { + // Add --user directory + // `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python//x64/ + // So if `findLocalTool` succeeded above, we must have a conformant `installDir` + const version = path.basename(path.dirname(installDir)); + const major = semver.major(version); + const minor = semver.minor(version); + const userScriptsDir = path.join(process.env['APPDATA'] || '', 'Python', `Python${major}${minor}`, 'Scripts'); + core.addPath(userScriptsDir); + } + // On Linux and macOS, pip will create the --user directory and add it to PATH as needed. } - // On Linux and macOS, pip will create the --user directory and add it to PATH as needed. const installed = versionFromPath(installDir); core.setOutput('python-version', installed); core.setOutput('python-path', pythonPath); @@ -64164,7 +64951,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.findAssetForMacOrLinux = exports.findAssetForWindows = exports.isArchPresentForMacOrLinux = exports.isArchPresentForWindows = exports.pypyVersionToSemantic = exports.getPyPyBinaryPath = exports.findRelease = exports.getAvailablePyPyVersions = exports.installPyPy = void 0; +exports.findAssetForMacOrLinux = exports.findAssetForWindows = exports.isArchPresentForMacOrLinux = exports.isArchPresentForWindows = exports.pypyVersionToSemantic = exports.getPyPyBinaryPath = exports.findRelease = exports.installPyPy = void 0; const path = __importStar(__nccwpck_require__(1017)); const core = __importStar(__nccwpck_require__(2186)); const tc = __importStar(__nccwpck_require__(7784)); @@ -64173,10 +64960,10 @@ const httpm = __importStar(__nccwpck_require__(9925)); const exec = __importStar(__nccwpck_require__(1514)); const fs_1 = __importDefault(__nccwpck_require__(7147)); const utils_1 = __nccwpck_require__(1314); -function installPyPy(pypyVersion, pythonVersion, architecture, releases) { +function installPyPy(pypyVersion, pythonVersion, architecture) { return __awaiter(this, void 0, void 0, function* () { let downloadDir; - releases = releases !== null && releases !== void 0 ? releases : (yield getAvailablePyPyVersions()); + const releases = yield getAvailablePyPyVersions(); if (!releases || releases.length === 0) { throw new Error('No release was found in PyPy version.json'); } @@ -64222,7 +65009,6 @@ function getAvailablePyPyVersions() { return response.result; }); } -exports.getAvailablePyPyVersions = getAvailablePyPyVersions; function createPyPySymlink(pypyBinaryPath, pythonVersion) { return __awaiter(this, void 0, void 0, function* () { const version = semver.coerce(pythonVersion); @@ -64345,33 +65131,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.installCpythonFromRelease = exports.getManifest = exports.findReleaseFromManifest = exports.MANIFEST_URL = void 0; +exports.installCpythonFromRelease = exports.findReleaseFromManifest = exports.MANIFEST_URL = void 0; const path = __importStar(__nccwpck_require__(1017)); const core = __importStar(__nccwpck_require__(2186)); const tc = __importStar(__nccwpck_require__(7784)); const exec = __importStar(__nccwpck_require__(1514)); const utils_1 = __nccwpck_require__(1314); const TOKEN = core.getInput('token'); -const AUTH = !TOKEN ? undefined : `token ${TOKEN}`; +const AUTH = !TOKEN || utils_1.isGhes() ? undefined : `token ${TOKEN}`; const MANIFEST_REPO_OWNER = 'actions'; const MANIFEST_REPO_NAME = 'python-versions'; const MANIFEST_REPO_BRANCH = 'main'; exports.MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`; -function findReleaseFromManifest(semanticVersionSpec, architecture, manifest) { +function findReleaseFromManifest(semanticVersionSpec, architecture) { return __awaiter(this, void 0, void 0, function* () { - if (!manifest) { - manifest = yield getManifest(); - } - const foundRelease = yield tc.findFromManifest(semanticVersionSpec, false, manifest, architecture); - return foundRelease; + const manifest = yield tc.getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH); + return yield tc.findFromManifest(semanticVersionSpec, false, manifest, architecture); }); } exports.findReleaseFromManifest = findReleaseFromManifest; -function getManifest() { - core.debug(`Getting manifest from ${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}@${MANIFEST_REPO_BRANCH}`); - return tc.getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH); -} -exports.getManifest = getManifest; function installPython(workingDirectory) { return __awaiter(this, void 0, void 0, function* () { const options = { @@ -64454,6 +65232,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.logWarning = void 0; const core = __importStar(__nccwpck_require__(2186)); const finder = __importStar(__nccwpck_require__(9996)); const finderPyPy = __importStar(__nccwpck_require__(4003)); @@ -64474,44 +65253,54 @@ function cacheDependencies(cache, pythonVersion) { } function resolveVersionInput() { let version = core.getInput('python-version'); - const versionFile = core.getInput('python-version-file'); + let versionFile = core.getInput('python-version-file'); if (version && versionFile) { - core.warning('Both python-version and python-version-file inputs are specified, only python-version will be used'); + core.warning('Both python-version and python-version-file inputs are specified, only python-version will be used.'); } if (version) { return version; } - const versionFilePath = path.join(process.env.GITHUB_WORKSPACE, versionFile || '.python-version'); - if (!fs_1.default.existsSync(versionFilePath)) { - throw new Error(`The specified python version file at: ${versionFilePath} does not exist`); + if (versionFile) { + if (!fs_1.default.existsSync(versionFile)) { + logWarning(`The specified python version file at: ${versionFile} doesn't exist. Attempting to find .python-version file.`); + versionFile = '.python-version'; + if (!fs_1.default.existsSync(versionFile)) { + throw new Error(`The ${versionFile} doesn't exist.`); + } + } + version = fs_1.default.readFileSync(versionFile, 'utf8'); + core.info(`Resolved ${versionFile} as ${version}`); + return version; } - version = fs_1.default.readFileSync(versionFilePath, 'utf8'); - core.info(`Resolved ${versionFile} as ${version}`); + core.warning("Neither 'python-version' nor 'python-version-file' inputs were supplied."); return version; } function run() { var _a; return __awaiter(this, void 0, void 0, function* () { - if ((_a = process.env.AGENT_TOOLSDIRECTORY) === null || _a === void 0 ? void 0 : _a.trim()) { - core.debug(`Python is expected to be installed into AGENT_TOOLSDIRECTORY=${process.env['AGENT_TOOLSDIRECTORY']}`); + // According to the README windows binaries do not require to be installed + // in the specific location, but Mac and Linux do + if (!utils_1.IS_WINDOWS && !((_a = process.env.AGENT_TOOLSDIRECTORY) === null || _a === void 0 ? void 0 : _a.trim())) { + if (utils_1.IS_LINUX) + process.env['AGENT_TOOLSDIRECTORY'] = '/opt/hostedtoolcache'; + else + process.env['AGENT_TOOLSDIRECTORY'] = '/Users/runner/hostedtoolcache'; process.env['RUNNER_TOOL_CACHE'] = process.env['AGENT_TOOLSDIRECTORY']; } - else { - core.debug(`Python is expected to be installed into RUNNER_TOOL_CACHE==${process.env['RUNNER_TOOL_CACHE']}`); - } + core.debug(`Python is expected to be installed into RUNNER_TOOL_CACHE=${process.env['RUNNER_TOOL_CACHE']}`); try { const version = resolveVersionInput(); - const checkLatest = core.getBooleanInput('check-latest'); if (version) { let pythonVersion; const arch = core.getInput('architecture') || os.arch(); + const updateEnvironment = core.getBooleanInput('update-environment'); if (isPyPyVersion(version)) { - const installed = yield finderPyPy.findPyPyVersion(version, arch, checkLatest); + const installed = yield finderPyPy.findPyPyVersion(version, arch, updateEnvironment); pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`; core.info(`Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`); } else { - const installed = yield finder.useCpythonVersion(version, arch, checkLatest); + const installed = yield finder.useCpythonVersion(version, arch, updateEnvironment); pythonVersion = installed.version; core.info(`Successfully set up ${installed.impl} (${pythonVersion})`); } @@ -64531,6 +65320,11 @@ function run() { } }); } +function logWarning(message) { + const warningPrefix = '[warning]'; + core.info(`${warningPrefix}${message}`); +} +exports.logWarning = logWarning; run(); diff --git a/package-lock.json b/package-lock.json index 9e3bc59..12dbdb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "4.0.0", "license": "MIT", "dependencies": { - "@actions/cache": "^2.0.2", + "@actions/cache": "^3.0.0", "@actions/core": "^1.7.0", "@actions/exec": "^1.1.0", "@actions/glob": "^0.2.0", @@ -31,14 +31,14 @@ } }, "node_modules/@actions/cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-2.0.2.tgz", - "integrity": "sha512-K1DCaW/OtHj5mV7hI7HEXiceX3rM4Nc0iG2hfYsrkEy6GiOeqlCC/LyICrBZIRDM6+vSrS12tg1ORl4hghomBA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-3.0.0.tgz", + "integrity": "sha512-GL9CT1Fnu+pqs8TTB621q8Xa8Cilw2n9MwvbgMedetH7L1q2n6jY61gzbwGbKgtVbp3gVJ12aNMi4osSGXx3KQ==", "dependencies": { "@actions/core": "^1.2.6", "@actions/exec": "^1.0.1", "@actions/glob": "^0.1.0", - "@actions/http-client": "^1.0.9", + "@actions/http-client": "^2.0.1", "@actions/io": "^1.0.1", "@azure/ms-rest-js": "^2.6.0", "@azure/storage-blob": "^12.8.0", @@ -55,6 +55,14 @@ "minimatch": "^3.0.4" } }, + "node_modules/@actions/cache/node_modules/@actions/http-client": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz", + "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==", + "dependencies": { + "tunnel": "^0.0.6" + } + }, "node_modules/@actions/cache/node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -11332,14 +11340,14 @@ }, "dependencies": { "@actions/cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-2.0.2.tgz", - "integrity": "sha512-K1DCaW/OtHj5mV7hI7HEXiceX3rM4Nc0iG2hfYsrkEy6GiOeqlCC/LyICrBZIRDM6+vSrS12tg1ORl4hghomBA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-3.0.0.tgz", + "integrity": "sha512-GL9CT1Fnu+pqs8TTB621q8Xa8Cilw2n9MwvbgMedetH7L1q2n6jY61gzbwGbKgtVbp3gVJ12aNMi4osSGXx3KQ==", "requires": { "@actions/core": "^1.2.6", "@actions/exec": "^1.0.1", "@actions/glob": "^0.1.0", - "@actions/http-client": "^1.0.9", + "@actions/http-client": "^2.0.1", "@actions/io": "^1.0.1", "@azure/ms-rest-js": "^2.6.0", "@azure/storage-blob": "^12.8.0", @@ -11356,6 +11364,14 @@ "minimatch": "^3.0.4" } }, + "@actions/http-client": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz", + "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==", + "requires": { + "tunnel": "^0.0.6" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", diff --git a/package.json b/package.json index d9c4ca3..0325cae 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "format": "prettier --write \"{,!(node_modules)/**/}*.ts\"", "format-check": "prettier --check \"{,!(node_modules)/**/}*.ts\"", "release": "ncc build -o dist/setup src/setup-python.ts && ncc build -o dist/cache-save src/cache-save.ts && git add -f dist/", - "test": "jest" + "test": "jest --coverage" }, "repository": { "type": "git", @@ -23,7 +23,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@actions/cache": "^2.0.2", + "@actions/cache": "^3.0.0", "@actions/core": "^1.7.0", "@actions/exec": "^1.1.0", "@actions/glob": "^0.2.0", diff --git a/src/cache-save.ts b/src/cache-save.ts index 0f9d037..6e96be1 100644 --- a/src/cache-save.ts +++ b/src/cache-save.ts @@ -43,17 +43,11 @@ async function saveCache(packageManager: string) { return; } - try { - await cache.saveCache(cachePaths, primaryKey); - core.info(`Cache saved with the key: ${primaryKey}`); - } catch (error) { - const err = error as Error; - if (err.name === cache.ReserveCacheError.name) { - core.info(err.message); - } else { - throw error; - } + const cacheId = await cache.saveCache(cachePaths, primaryKey); + if (cacheId == -1) { + return; } + core.info(`Cache saved with the key: ${primaryKey}`); } function isCacheDirectoryExists(cacheDirectory: string[]) { diff --git a/src/find-pypy.ts b/src/find-pypy.ts index e790c44..20b9821 100644 --- a/src/find-pypy.ts +++ b/src/find-pypy.ts @@ -22,6 +22,7 @@ interface IPyPyVersionSpec { export async function findPyPyVersion( versionSpec: string, architecture: string, + updateEnvironment: boolean, checkLatest: boolean ): Promise<{resolvedPyPyVersion: string; resolvedPythonVersion: string}> { let resolvedPyPyVersion = ''; @@ -82,10 +83,18 @@ export async function findPyPyVersion( `python${binaryExtension}` ); const pythonLocation = pypyInstall.getPyPyBinaryPath(installDir); - core.exportVariable('pythonLocation', installDir); - core.exportVariable('PKG_CONFIG_PATH', pythonLocation + '/lib/pkgconfig'); - core.addPath(pythonLocation); - core.addPath(_binDir); + if (updateEnvironment) { + core.exportVariable('pythonLocation', installDir); + // https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython + core.exportVariable('Python_ROOT_DIR', installDir); + // https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython2 + core.exportVariable('Python2_ROOT_DIR', installDir); + // https://cmake.org/cmake/help/latest/module/FindPython3.html#module:FindPython3 + core.exportVariable('Python3_ROOT_DIR', installDir); + core.exportVariable('PKG_CONFIG_PATH', pythonLocation + '/lib/pkgconfig'); + core.addPath(pythonLocation); + core.addPath(_binDir); + } core.setOutput('python-version', 'pypy' + resolvedPyPyVersion.trim()); core.setOutput('python-path', pythonPath); diff --git a/src/find-python.ts b/src/find-python.ts index f6d0673..4e54a94 100644 --- a/src/find-python.ts +++ b/src/find-python.ts @@ -33,6 +33,7 @@ function binDir(installDir: string): string { export async function useCpythonVersion( version: string, architecture: string, + updateEnvironment: boolean, checkLatest: boolean ): Promise { let manifest: tc.IToolRelease[] | null = null; @@ -92,46 +93,55 @@ export async function useCpythonVersion( ); } - core.exportVariable('pythonLocation', installDir); - core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig'); - - if (IS_LINUX) { - const libPath = process.env.LD_LIBRARY_PATH - ? `:${process.env.LD_LIBRARY_PATH}` - : ''; - const pyLibPath = path.join(installDir, 'lib'); - - if (!libPath.split(':').includes(pyLibPath)) { - core.exportVariable('LD_LIBRARY_PATH', pyLibPath + libPath); - } - } - const _binDir = binDir(installDir); const binaryExtension = IS_WINDOWS ? '.exe' : ''; const pythonPath = path.join( IS_WINDOWS ? installDir : _binDir, `python${binaryExtension}` ); - core.addPath(installDir); - core.addPath(_binDir); + if (updateEnvironment) { + core.exportVariable('pythonLocation', installDir); + core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig'); + core.exportVariable('pythonLocation', installDir); + // https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython + core.exportVariable('Python_ROOT_DIR', installDir); + // https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython2 + core.exportVariable('Python2_ROOT_DIR', installDir); + // https://cmake.org/cmake/help/latest/module/FindPython3.html#module:FindPython3 + core.exportVariable('Python3_ROOT_DIR', installDir); + core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig'); - if (IS_WINDOWS) { - // Add --user directory - // `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python//x64/ - // So if `findLocalTool` succeeded above, we must have a conformant `installDir` - const version = path.basename(path.dirname(installDir)); - const major = semver.major(version); - const minor = semver.minor(version); + if (IS_LINUX) { + const libPath = process.env.LD_LIBRARY_PATH + ? `:${process.env.LD_LIBRARY_PATH}` + : ''; + const pyLibPath = path.join(installDir, 'lib'); - const userScriptsDir = path.join( - process.env['APPDATA'] || '', - 'Python', - `Python${major}${minor}`, - 'Scripts' - ); - core.addPath(userScriptsDir); + if (!libPath.split(':').includes(pyLibPath)) { + core.exportVariable('LD_LIBRARY_PATH', pyLibPath + libPath); + } + } + core.addPath(installDir); + core.addPath(_binDir); + + if (IS_WINDOWS) { + // Add --user directory + // `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python//x64/ + // So if `findLocalTool` succeeded above, we must have a conformant `installDir` + const version = path.basename(path.dirname(installDir)); + const major = semver.major(version); + const minor = semver.minor(version); + + const userScriptsDir = path.join( + process.env['APPDATA'] || '', + 'Python', + `Python${major}${minor}`, + 'Scripts' + ); + core.addPath(userScriptsDir); + } + // On Linux and macOS, pip will create the --user directory and add it to PATH as needed. } - // On Linux and macOS, pip will create the --user directory and add it to PATH as needed. const installed = versionFromPath(installDir); core.setOutput('python-version', installed); diff --git a/src/install-python.ts b/src/install-python.ts index aa6ab2d..6e5c851 100644 --- a/src/install-python.ts +++ b/src/install-python.ts @@ -3,10 +3,10 @@ import * as core from '@actions/core'; import * as tc from '@actions/tool-cache'; import * as exec from '@actions/exec'; import {ExecOptions} from '@actions/exec/lib/interfaces'; -import {IS_WINDOWS, IS_LINUX} from './utils'; +import {IS_WINDOWS, IS_LINUX, isGhes} from './utils'; const TOKEN = core.getInput('token'); -const AUTH = !TOKEN ? undefined : `token ${TOKEN}`; +const AUTH = !TOKEN || isGhes() ? undefined : `token ${TOKEN}`; const MANIFEST_REPO_OWNER = 'actions'; const MANIFEST_REPO_NAME = 'python-versions'; const MANIFEST_REPO_BRANCH = 'main'; diff --git a/src/setup-python.ts b/src/setup-python.ts index befc0d9..d8888da 100644 --- a/src/setup-python.ts +++ b/src/setup-python.ts @@ -5,7 +5,7 @@ import * as path from 'path'; import * as os from 'os'; import fs from 'fs'; import {getCacheDistributor} from './cache-distributions/cache-factory'; -import {isCacheFeatureAvailable} from './utils'; +import {isCacheFeatureAvailable, IS_LINUX, IS_WINDOWS} from './utils'; function isPyPyVersion(versionSpec: string) { return versionSpec.startsWith('pypy'); @@ -24,11 +24,11 @@ async function cacheDependencies(cache: string, pythonVersion: string) { function resolveVersionInput(): string { let version = core.getInput('python-version'); - const versionFile = core.getInput('python-version-file'); + let versionFile = core.getInput('python-version-file'); if (version && versionFile) { core.warning( - 'Both python-version and python-version-file inputs are specified, only python-version will be used' + 'Both python-version and python-version-file inputs are specified, only python-version will be used.' ); } @@ -36,32 +36,41 @@ function resolveVersionInput(): string { return version; } - const versionFilePath = path.join( - process.env.GITHUB_WORKSPACE!, - versionFile || '.python-version' - ); - if (!fs.existsSync(versionFilePath)) { - throw new Error( - `The specified python version file at: ${versionFilePath} does not exist` - ); + if (versionFile) { + if (!fs.existsSync(versionFile)) { + logWarning( + `The specified python version file at: ${versionFile} doesn't exist. Attempting to find .python-version file.` + ); + versionFile = '.python-version'; + if (!fs.existsSync(versionFile)) { + throw new Error(`The ${versionFile} doesn't exist.`); + } + } + + version = fs.readFileSync(versionFile, 'utf8'); + core.info(`Resolved ${versionFile} as ${version}`); + + return version; } - version = fs.readFileSync(versionFilePath, 'utf8'); - core.info(`Resolved ${versionFile} as ${version}`); + + core.warning( + "Neither 'python-version' nor 'python-version-file' inputs were supplied." + ); return version; } async function run() { - if (process.env.AGENT_TOOLSDIRECTORY?.trim()) { - core.debug( - `Python is expected to be installed into AGENT_TOOLSDIRECTORY=${process.env['AGENT_TOOLSDIRECTORY']}` - ); + // According to the README windows binaries do not require to be installed + // in the specific location, but Mac and Linux do + if (!IS_WINDOWS && !process.env.AGENT_TOOLSDIRECTORY?.trim()) { + if (IS_LINUX) process.env['AGENT_TOOLSDIRECTORY'] = '/opt/hostedtoolcache'; + else process.env['AGENT_TOOLSDIRECTORY'] = '/Users/runner/hostedtoolcache'; process.env['RUNNER_TOOL_CACHE'] = process.env['AGENT_TOOLSDIRECTORY']; - } else { - core.debug( - `Python is expected to be installed into RUNNER_TOOL_CACHE==${process.env['RUNNER_TOOL_CACHE']}` - ); } + core.debug( + `Python is expected to be installed into RUNNER_TOOL_CACHE=${process.env['RUNNER_TOOL_CACHE']}` + ); try { const version = resolveVersionInput(); const checkLatest = core.getBooleanInput('check-latest'); @@ -69,10 +78,12 @@ async function run() { if (version) { let pythonVersion: string; const arch: string = core.getInput('architecture') || os.arch(); + const updateEnvironment = core.getBooleanInput('update-environment'); if (isPyPyVersion(version)) { const installed = await finderPyPy.findPyPyVersion( version, arch, + updateEnvironment, checkLatest ); pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`; @@ -83,6 +94,7 @@ async function run() { const installed = await finder.useCpythonVersion( version, arch, + updateEnvironment, checkLatest ); pythonVersion = installed.version; @@ -105,4 +117,9 @@ async function run() { } } +export function logWarning(message: string): void { + const warningPrefix = '[warning]'; + core.info(`${warningPrefix}${message}`); +} + run();