mirror of
				https://git.mirrors.martin98.com/https://github.com/actions/setup-python
				synced 2025-10-31 15:01:07 +08:00 
			
		
		
		
	Add GraalPy support (#694)
* Add support for graalpy * add graalpy test workflow * format, lint and build * symlink graalpy binaries names * fix macos names for graalpy * Don't attempt to update pip for graalpy * Remove test schedule * Extract common getBinaryDirectory function for PyPy and GraalPy * Clean up and format * Pass GitHub token to GraalPy queries * Utilize pagination when querying GraalPy GitHub releases * Build * Fix lint errors * Deal with possible multiple artifacts for a single releases * Skip few GraalPy tests on windows - we don't have a windows release yet * Fix GraalPy test on Mac OS * Build * Skip one more GraalPy test on windows --------- Co-authored-by: Michael Simacek <michael.simacek@oracle.com>
This commit is contained in:
		
							parent
							
								
									3467d92d48
								
							
						
					
					
						commit
						5f2af211d6
					
				
							
								
								
									
										116
									
								
								.github/workflows/test-graalpy.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								.github/workflows/test-graalpy.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,116 @@ | ||||
| name: Validate GraalPy e2e | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - main | ||||
|     paths-ignore: | ||||
|       - '**.md' | ||||
|   pull_request: | ||||
|     paths-ignore: | ||||
|       - '**.md' | ||||
| 
 | ||||
| jobs: | ||||
|   setup-graalpy: | ||||
|     name: Setup GraalPy ${{ matrix.graalpy }} ${{ matrix.os }} | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         os: [macos-latest, ubuntu-20.04, ubuntu-latest] | ||||
|         graalpy: | ||||
|           - 'graalpy-23.0' | ||||
|           - 'graalpy-22.3' | ||||
| 
 | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
| 
 | ||||
|       - name: setup-python ${{ matrix.graalpy }} | ||||
|         id: setup-python | ||||
|         uses: ./ | ||||
|         with: | ||||
|           python-version: ${{ matrix.graalpy }} | ||||
| 
 | ||||
|       - name: Check python-path | ||||
|         run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}' | ||||
|         shell: bash | ||||
| 
 | ||||
|       - name: GraalPy and Python version | ||||
|         run: python --version | ||||
| 
 | ||||
|       - name: Run simple code | ||||
|         run: python -c 'import math; print(math.factorial(5))' | ||||
| 
 | ||||
|       - name: Assert GraalPy is running | ||||
|         run: | | ||||
|           import platform | ||||
|           assert platform.python_implementation().lower() == "graalvm" | ||||
|         shell: python | ||||
| 
 | ||||
|       - name: Assert expected binaries (or symlinks) are present | ||||
|         run: | | ||||
|           EXECUTABLE=${{ matrix.graalpy }} | ||||
|           EXECUTABLE=${EXECUTABLE/graalpy-/graalpy}  # remove the first '-' in "graalpy-X.Y" -> "graalpyX.Y" to match executable name | ||||
|           EXECUTABLE=${EXECUTABLE%%-*}  # remove any -* suffixe | ||||
|           ${EXECUTABLE} --version | ||||
|         shell: bash | ||||
| 
 | ||||
|   setup-graalpy-noenv: | ||||
|     name: Setup GraalPy ${{ matrix.graalpy }} ${{ matrix.os }} (noenv) | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         os: [macos-latest, ubuntu-20.04, ubuntu-latest] | ||||
|         graalpy: ['graalpy23.0', 'graalpy22.3'] | ||||
| 
 | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
| 
 | ||||
|       - name: setup-python ${{ matrix.graalpy }} | ||||
|         id: setup-python | ||||
|         uses: ./ | ||||
|         with: | ||||
|           python-version: ${{ matrix.graalpy }} | ||||
|           update-environment: false | ||||
| 
 | ||||
|       - name: GraalPy 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: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         os: [ubuntu-latest, macos-latest] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|       - name: Setup GraalPy and check latest | ||||
|         uses: ./ | ||||
|         with: | ||||
|           python-version: 'graalpy-23.x' | ||||
|           check-latest: true | ||||
|       - name: GraalPy and Python version | ||||
|         run: python --version | ||||
| 
 | ||||
|       - name: Run simple code | ||||
|         run: python -c 'import math; print(math.factorial(5))' | ||||
| 
 | ||||
|       - name: Assert GraalPy is running | ||||
|         run: | | ||||
|           import platform | ||||
|           assert platform.python_implementation().lower() == "graalvm" | ||||
|         shell: python | ||||
| 
 | ||||
|       - name: Assert expected binaries (or symlinks) are present | ||||
|         run: | | ||||
|           EXECUTABLE="graalpy-23.0" | ||||
|           EXECUTABLE=${EXECUTABLE/-/}  # remove the first '-' in "graalpy-X.Y" -> "graalpyX.Y" to match executable name | ||||
|           EXECUTABLE=${EXECUTABLE%%-*}  # remove any -* suffixe | ||||
|           ${EXECUTABLE} --version | ||||
|         shell: bash | ||||
							
								
								
									
										5798
									
								
								__tests__/data/graalpy.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5798
									
								
								__tests__/data/graalpy.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										378
									
								
								__tests__/find-graalpy.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										378
									
								
								__tests__/find-graalpy.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,378 @@ | ||||
| import fs from 'fs'; | ||||
| 
 | ||||
| import {HttpClient} from '@actions/http-client'; | ||||
| import * as ifm from '@actions/http-client/interfaces'; | ||||
| import * as tc from '@actions/tool-cache'; | ||||
| import * as exec from '@actions/exec'; | ||||
| import * as core from '@actions/core'; | ||||
| 
 | ||||
| import * as path from 'path'; | ||||
| import * as semver from 'semver'; | ||||
| 
 | ||||
| import * as finder from '../src/find-graalpy'; | ||||
| import {IGraalPyManifestRelease, IS_WINDOWS} from '../src/utils'; | ||||
| 
 | ||||
| import manifestData from './data/graalpy.json'; | ||||
| 
 | ||||
| const architecture = 'x64'; | ||||
| 
 | ||||
| const toolDir = path.join(__dirname, 'runner', 'tools'); | ||||
| const tempDir = path.join(__dirname, 'runner', 'temp'); | ||||
| 
 | ||||
| /* GraalPy doesn't have a windows release yet */ | ||||
| const describeSkipOnWindows = IS_WINDOWS ? describe.skip : describe; | ||||
| 
 | ||||
| describe('parseGraalPyVersion', () => { | ||||
|   it.each([ | ||||
|     ['graalpy-23', '23'], | ||||
|     ['graalpy-23.0', '23.0'], | ||||
|     ['graalpy23.0', '23.0'] | ||||
|   ])('%s -> %s', (input, expected) => { | ||||
|     expect(finder.parseGraalPyVersion(input)).toEqual(expected); | ||||
|   }); | ||||
| 
 | ||||
|   it.each(['', 'graalpy-', 'graalpy', 'p', 'notgraalpy-'])( | ||||
|     'throw on invalid input "%s"', | ||||
|     input => { | ||||
|       expect(() => finder.parseGraalPyVersion(input)).toThrow( | ||||
|         "Invalid 'version' property for GraalPy. GraalPy version should be specified as 'graalpy<python-version>' or 'graalpy-<python-version>'. See README for examples and documentation." | ||||
|       ); | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
| 
 | ||||
| describe('findGraalPyToolCache', () => { | ||||
|   const actualGraalPyVersion = '23.0.0'; | ||||
|   const graalpyPath = path.join('GraalPy', actualGraalPyVersion, architecture); | ||||
|   let tcFind: jest.SpyInstance; | ||||
|   let infoSpy: jest.SpyInstance; | ||||
|   let warningSpy: jest.SpyInstance; | ||||
|   let debugSpy: jest.SpyInstance; | ||||
|   let addPathSpy: jest.SpyInstance; | ||||
|   let exportVariableSpy: jest.SpyInstance; | ||||
|   let setOutputSpy: jest.SpyInstance; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     tcFind = jest.spyOn(tc, 'find'); | ||||
|     tcFind.mockImplementation((toolname: string, pythonVersion: string) => { | ||||
|       const semverVersion = new semver.Range(pythonVersion); | ||||
|       return semver.satisfies(actualGraalPyVersion, semverVersion) | ||||
|         ? graalpyPath | ||||
|         : ''; | ||||
|     }); | ||||
| 
 | ||||
|     infoSpy = jest.spyOn(core, 'info'); | ||||
|     infoSpy.mockImplementation(() => null); | ||||
| 
 | ||||
|     warningSpy = jest.spyOn(core, 'warning'); | ||||
|     warningSpy.mockImplementation(() => null); | ||||
| 
 | ||||
|     debugSpy = jest.spyOn(core, 'debug'); | ||||
|     debugSpy.mockImplementation(() => null); | ||||
| 
 | ||||
|     addPathSpy = jest.spyOn(core, 'addPath'); | ||||
|     addPathSpy.mockImplementation(() => null); | ||||
| 
 | ||||
|     exportVariableSpy = jest.spyOn(core, 'exportVariable'); | ||||
|     exportVariableSpy.mockImplementation(() => null); | ||||
| 
 | ||||
|     setOutputSpy = jest.spyOn(core, 'setOutput'); | ||||
|     setOutputSpy.mockImplementation(() => null); | ||||
|   }); | ||||
| 
 | ||||
|   afterEach(() => { | ||||
|     jest.resetAllMocks(); | ||||
|     jest.clearAllMocks(); | ||||
|     jest.restoreAllMocks(); | ||||
|   }); | ||||
| 
 | ||||
|   it('GraalPy exists on the path and versions are satisfied', () => { | ||||
|     expect(finder.findGraalPyToolCache('23.0.0', architecture)).toEqual({ | ||||
|       installDir: graalpyPath, | ||||
|       resolvedGraalPyVersion: actualGraalPyVersion | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   it('GraalPy exists on the path and versions are satisfied with semver', () => { | ||||
|     expect(finder.findGraalPyToolCache('23.0', architecture)).toEqual({ | ||||
|       installDir: graalpyPath, | ||||
|       resolvedGraalPyVersion: actualGraalPyVersion | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   it("GraalPy exists on the path, but version doesn't match", () => { | ||||
|     expect(finder.findGraalPyToolCache('22.3', architecture)).toEqual({ | ||||
|       installDir: '', | ||||
|       resolvedGraalPyVersion: '' | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describeSkipOnWindows('findGraalPyVersion', () => { | ||||
|   let getBooleanInputSpy: jest.SpyInstance; | ||||
|   let warningSpy: jest.SpyInstance; | ||||
|   let debugSpy: jest.SpyInstance; | ||||
|   let infoSpy: jest.SpyInstance; | ||||
|   let addPathSpy: jest.SpyInstance; | ||||
|   let exportVariableSpy: jest.SpyInstance; | ||||
|   let setOutputSpy: jest.SpyInstance; | ||||
|   let tcFind: jest.SpyInstance; | ||||
|   let spyExtractZip: jest.SpyInstance; | ||||
|   let spyExtractTar: jest.SpyInstance; | ||||
|   let spyHttpClient: jest.SpyInstance; | ||||
|   let spyExistsSync: jest.SpyInstance; | ||||
|   let spyExec: jest.SpyInstance; | ||||
|   let spySymlinkSync: jest.SpyInstance; | ||||
|   let spyDownloadTool: jest.SpyInstance; | ||||
|   let spyFsReadDir: 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'); | ||||
|     getBooleanInputSpy.mockImplementation(() => false); | ||||
| 
 | ||||
|     infoSpy = jest.spyOn(core, 'info'); | ||||
|     infoSpy.mockImplementation(() => {}); | ||||
| 
 | ||||
|     warningSpy = jest.spyOn(core, 'warning'); | ||||
|     warningSpy.mockImplementation(() => null); | ||||
| 
 | ||||
|     debugSpy = jest.spyOn(core, 'debug'); | ||||
|     debugSpy.mockImplementation(() => null); | ||||
| 
 | ||||
|     addPathSpy = jest.spyOn(core, 'addPath'); | ||||
|     addPathSpy.mockImplementation(() => null); | ||||
| 
 | ||||
|     exportVariableSpy = jest.spyOn(core, 'exportVariable'); | ||||
|     exportVariableSpy.mockImplementation(() => null); | ||||
| 
 | ||||
|     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); | ||||
|       let graalpyPath = ''; | ||||
|       if (semver.satisfies('23.0.0', semverRange)) { | ||||
|         graalpyPath = path.join(toolDir, 'GraalPy', '23.0.0', architecture); | ||||
|       } | ||||
|       return graalpyPath; | ||||
|     }); | ||||
| 
 | ||||
|     spyDownloadTool = jest.spyOn(tc, 'downloadTool'); | ||||
|     spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy')); | ||||
| 
 | ||||
|     spyExtractZip = jest.spyOn(tc, 'extractZip'); | ||||
|     spyExtractZip.mockImplementation(() => tempDir); | ||||
| 
 | ||||
|     spyExtractTar = jest.spyOn(tc, 'extractTar'); | ||||
|     spyExtractTar.mockImplementation(() => tempDir); | ||||
| 
 | ||||
|     spyFsReadDir = jest.spyOn(fs, 'readdirSync'); | ||||
|     spyFsReadDir.mockImplementation((directory: string) => ['GraalPyTest']); | ||||
| 
 | ||||
|     spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); | ||||
|     spyHttpClient.mockImplementation( | ||||
|       async (): Promise<ifm.ITypedResponse<IGraalPyManifestRelease[]>> => { | ||||
|         const result = JSON.stringify(manifestData); | ||||
|         return { | ||||
|           statusCode: 200, | ||||
|           headers: {}, | ||||
|           result: JSON.parse(result) as IGraalPyManifestRelease[] | ||||
|         }; | ||||
|       } | ||||
|     ); | ||||
| 
 | ||||
|     spyExec = jest.spyOn(exec, 'exec'); | ||||
|     spyExec.mockImplementation(() => undefined); | ||||
| 
 | ||||
|     spySymlinkSync = jest.spyOn(fs, 'symlinkSync'); | ||||
|     spySymlinkSync.mockImplementation(() => undefined); | ||||
| 
 | ||||
|     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 GraalPy in toolcache', async () => { | ||||
|     await expect( | ||||
|       finder.findGraalPyVersion( | ||||
|         'graalpy-23.0', | ||||
|         architecture, | ||||
|         true, | ||||
|         false, | ||||
|         false | ||||
|       ) | ||||
|     ).resolves.toEqual('23.0.0'); | ||||
|     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.findGraalPyVersion('graalpy-x23', architecture, true, false, false) | ||||
|     ).rejects.toThrow(); | ||||
|   }); | ||||
| 
 | ||||
|   it('found and install successfully', async () => { | ||||
|     spyCacheDir = jest.spyOn(tc, 'cacheDir'); | ||||
|     spyCacheDir.mockImplementation(() => | ||||
|       path.join(toolDir, 'GraalPy', '23.0.0', architecture) | ||||
|     ); | ||||
|     spyChmodSync = jest.spyOn(fs, 'chmodSync'); | ||||
|     spyChmodSync.mockImplementation(() => undefined); | ||||
|     await expect( | ||||
|       finder.findGraalPyVersion( | ||||
|         'graalpy-23.0.0', | ||||
|         architecture, | ||||
|         true, | ||||
|         false, | ||||
|         false | ||||
|       ) | ||||
|     ).resolves.toEqual('23.0.0'); | ||||
|     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, 'GraalPy', '23.0.0', architecture) | ||||
|     ); | ||||
|     spyChmodSync = jest.spyOn(fs, 'chmodSync'); | ||||
|     spyChmodSync.mockImplementation(() => undefined); | ||||
|     await expect( | ||||
|       finder.findGraalPyVersion( | ||||
|         'graalpy-23.0.0', | ||||
|         architecture, | ||||
|         false, | ||||
|         false, | ||||
|         false | ||||
|       ) | ||||
|     ).resolves.toEqual('23.0.0'); | ||||
|     expect(spyCoreAddPath).not.toHaveBeenCalled(); | ||||
|     expect(spyCoreExportVariable).not.toHaveBeenCalled(); | ||||
|   }); | ||||
| 
 | ||||
|   it('throw if release is not found', async () => { | ||||
|     await expect( | ||||
|       finder.findGraalPyVersion( | ||||
|         'graalpy-19.0.0', | ||||
|         architecture, | ||||
|         true, | ||||
|         false, | ||||
|         false | ||||
|       ) | ||||
|     ).rejects.toThrow( | ||||
|       `GraalPy version 19.0.0 with arch ${architecture} not found` | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   it('check-latest enabled version found and used from toolcache', async () => { | ||||
|     await expect( | ||||
|       finder.findGraalPyVersion( | ||||
|         'graalpy-23.0.0', | ||||
|         architecture, | ||||
|         false, | ||||
|         true, | ||||
|         false | ||||
|       ) | ||||
|     ).resolves.toEqual('23.0.0'); | ||||
| 
 | ||||
|     expect(infoSpy).toHaveBeenCalledWith('Resolved as GraalPy 23.0.0'); | ||||
|   }); | ||||
| 
 | ||||
|   it('check-latest enabled version found and install successfully', async () => { | ||||
|     spyCacheDir = jest.spyOn(tc, 'cacheDir'); | ||||
|     spyCacheDir.mockImplementation(() => | ||||
|       path.join(toolDir, 'GraalPy', '23.0.0', architecture) | ||||
|     ); | ||||
|     spyChmodSync = jest.spyOn(fs, 'chmodSync'); | ||||
|     spyChmodSync.mockImplementation(() => undefined); | ||||
|     await expect( | ||||
|       finder.findGraalPyVersion( | ||||
|         'graalpy-23.0.0', | ||||
|         architecture, | ||||
|         false, | ||||
|         true, | ||||
|         false | ||||
|       ) | ||||
|     ).resolves.toEqual('23.0.0'); | ||||
|     expect(infoSpy).toHaveBeenCalledWith('Resolved as GraalPy 23.0.0'); | ||||
|   }); | ||||
| 
 | ||||
|   it('check-latest enabled version is not found and used from toolcache', async () => { | ||||
|     tcFind.mockImplementationOnce((tool: string, version: string) => { | ||||
|       const semverRange = new semver.Range(version); | ||||
|       let graalpyPath = ''; | ||||
|       if (semver.satisfies('22.3.4', semverRange)) { | ||||
|         graalpyPath = path.join(toolDir, 'GraalPy', '22.3.4', architecture); | ||||
|       } | ||||
|       return graalpyPath; | ||||
|     }); | ||||
|     await expect( | ||||
|       finder.findGraalPyVersion( | ||||
|         'graalpy-22.3.4', | ||||
|         architecture, | ||||
|         false, | ||||
|         true, | ||||
|         false | ||||
|       ) | ||||
|     ).resolves.toEqual('22.3.4'); | ||||
| 
 | ||||
|     expect(infoSpy).toHaveBeenCalledWith( | ||||
|       'Failed to resolve GraalPy 22.3.4 from manifest' | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   it('found and install successfully, pre-release fallback', async () => { | ||||
|     spyCacheDir = jest.spyOn(tc, 'cacheDir'); | ||||
|     spyCacheDir.mockImplementation(() => | ||||
|       path.join(toolDir, 'GraalPy', '23.1', architecture) | ||||
|     ); | ||||
|     spyChmodSync = jest.spyOn(fs, 'chmodSync'); | ||||
|     spyChmodSync.mockImplementation(() => undefined); | ||||
|     await expect( | ||||
|       finder.findGraalPyVersion( | ||||
|         'graalpy23.1', | ||||
|         architecture, | ||||
|         false, | ||||
|         false, | ||||
|         false | ||||
|       ) | ||||
|     ).rejects.toThrow(); | ||||
|     await expect( | ||||
|       finder.findGraalPyVersion('graalpy23.1', architecture, false, false, true) | ||||
|     ).resolves.toEqual('23.1.0-a.1'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										256
									
								
								__tests__/install-graalpy.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								__tests__/install-graalpy.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,256 @@ | ||||
| import fs from 'fs'; | ||||
| 
 | ||||
| import {HttpClient} from '@actions/http-client'; | ||||
| import * as ifm from '@actions/http-client/interfaces'; | ||||
| import * as tc from '@actions/tool-cache'; | ||||
| import * as exec from '@actions/exec'; | ||||
| import * as core from '@actions/core'; | ||||
| import * as path from 'path'; | ||||
| 
 | ||||
| import * as installer from '../src/install-graalpy'; | ||||
| import { | ||||
|   IGraalPyManifestRelease, | ||||
|   IGraalPyManifestAsset, | ||||
|   IS_WINDOWS | ||||
| } from '../src/utils'; | ||||
| 
 | ||||
| import manifestData from './data/graalpy.json'; | ||||
| 
 | ||||
| const architecture = 'x64'; | ||||
| 
 | ||||
| const toolDir = path.join(__dirname, 'runner', 'tools'); | ||||
| const tempDir = path.join(__dirname, 'runner', 'temp'); | ||||
| 
 | ||||
| /* GraalPy doesn't have a windows release yet */ | ||||
| const describeSkipOnWindows = IS_WINDOWS ? describe.skip : describe; | ||||
| 
 | ||||
| describe('graalpyVersionToSemantic', () => { | ||||
|   it.each([ | ||||
|     ['23.0.0a1', '23.0.0a1'], | ||||
|     ['23.0.0', '23.0.0'], | ||||
|     ['23.0.x', '23.0.x'], | ||||
|     ['23.x', '23.x'] | ||||
|   ])('%s -> %s', (input, expected) => { | ||||
|     expect(installer.graalPyTagToVersion(input)).toEqual(expected); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describeSkipOnWindows('findRelease', () => { | ||||
|   const result = JSON.stringify(manifestData); | ||||
|   const releases = JSON.parse(result) as IGraalPyManifestRelease[]; | ||||
|   const extension = 'tar.gz'; | ||||
|   const arch = installer.toGraalPyArchitecture(architecture); | ||||
|   const platform = installer.toGraalPyPlatform(process.platform); | ||||
|   const extensionName = `${platform}-${arch}.${extension}`; | ||||
|   const files: IGraalPyManifestAsset = { | ||||
|     name: `graalpython-23.0.0-${extensionName}`, | ||||
|     browser_download_url: `https://github.com/oracle/graalpython/releases/download/graal-23.0.0/graalpython-23.0.0-${extensionName}` | ||||
|   }; | ||||
|   const filesRC1: IGraalPyManifestAsset = { | ||||
|     name: `graalpython-23.1.0a1-${extensionName}`, | ||||
|     browser_download_url: `https://github.com/oracle/graalpython/releases/download/graal-23.1.0a1/graalpython-23.1.0a1-${extensionName}` | ||||
|   }; | ||||
| 
 | ||||
|   let warningSpy: jest.SpyInstance; | ||||
|   let debugSpy: jest.SpyInstance; | ||||
|   let infoSpy: jest.SpyInstance; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     infoSpy = jest.spyOn(core, 'info'); | ||||
|     infoSpy.mockImplementation(() => {}); | ||||
| 
 | ||||
|     warningSpy = jest.spyOn(core, 'warning'); | ||||
|     warningSpy.mockImplementation(() => null); | ||||
| 
 | ||||
|     debugSpy = jest.spyOn(core, 'debug'); | ||||
|     debugSpy.mockImplementation(() => null); | ||||
|   }); | ||||
| 
 | ||||
|   it("GraalPy version doesn't match", () => { | ||||
|     const graalpyVersion = '12.0.0'; | ||||
|     expect( | ||||
|       installer.findRelease(releases, graalpyVersion, architecture, false) | ||||
|     ).toEqual(null); | ||||
|   }); | ||||
| 
 | ||||
|   it('GraalPy version matches', () => { | ||||
|     const graalpyVersion = '23.0.0'; | ||||
|     expect( | ||||
|       installer.findRelease(releases, graalpyVersion, architecture, false) | ||||
|     ).toMatchObject({ | ||||
|       foundAsset: files, | ||||
|       resolvedGraalPyVersion: graalpyVersion | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   it('Preview version of GraalPy is found', () => { | ||||
|     const graalpyVersion = installer.graalPyTagToVersion('vm-23.1.0a1'); | ||||
|     expect( | ||||
|       installer.findRelease(releases, graalpyVersion, architecture, false) | ||||
|     ).toMatchObject({ | ||||
|       foundAsset: { | ||||
|         name: `graalpython-23.1.0a1-${extensionName}`, | ||||
|         browser_download_url: `https://github.com/oracle/graalpython/releases/download/graal-23.1.0a1/graalpython-23.1.0a1-${extensionName}` | ||||
|       }, | ||||
|       resolvedGraalPyVersion: '23.1.0-a.1' | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   it('Latest GraalPy is found', () => { | ||||
|     const graalpyVersion = 'x'; | ||||
|     expect( | ||||
|       installer.findRelease(releases, graalpyVersion, architecture, false) | ||||
|     ).toMatchObject({ | ||||
|       foundAsset: files, | ||||
|       resolvedGraalPyVersion: '23.0.0' | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   it('GraalPy version matches semver (pre-release)', () => { | ||||
|     const graalpyVersion = '23.1.x'; | ||||
|     expect( | ||||
|       installer.findRelease(releases, graalpyVersion, architecture, false) | ||||
|     ).toBeNull(); | ||||
|     expect( | ||||
|       installer.findRelease(releases, graalpyVersion, architecture, true) | ||||
|     ).toMatchObject({ | ||||
|       foundAsset: filesRC1, | ||||
|       resolvedGraalPyVersion: '23.1.0-a.1' | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describeSkipOnWindows('installGraalPy', () => { | ||||
|   let tcFind: jest.SpyInstance; | ||||
|   let warningSpy: jest.SpyInstance; | ||||
|   let debugSpy: jest.SpyInstance; | ||||
|   let infoSpy: jest.SpyInstance; | ||||
|   let spyExtractZip: jest.SpyInstance; | ||||
|   let spyExtractTar: jest.SpyInstance; | ||||
|   let spyFsReadDir: jest.SpyInstance; | ||||
|   let spyFsWriteFile: jest.SpyInstance; | ||||
|   let spyHttpClient: jest.SpyInstance; | ||||
|   let spyExistsSync: jest.SpyInstance; | ||||
|   let spyExec: jest.SpyInstance; | ||||
|   let spySymlinkSync: jest.SpyInstance; | ||||
|   let spyDownloadTool: jest.SpyInstance; | ||||
|   let spyCacheDir: jest.SpyInstance; | ||||
|   let spyChmodSync: jest.SpyInstance; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     tcFind = jest.spyOn(tc, 'find'); | ||||
|     tcFind.mockImplementation(() => | ||||
|       path.join('GraalPy', '3.6.12', architecture) | ||||
|     ); | ||||
| 
 | ||||
|     spyDownloadTool = jest.spyOn(tc, 'downloadTool'); | ||||
|     spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy')); | ||||
| 
 | ||||
|     spyExtractZip = jest.spyOn(tc, 'extractZip'); | ||||
|     spyExtractZip.mockImplementation(() => tempDir); | ||||
| 
 | ||||
|     spyExtractTar = jest.spyOn(tc, 'extractTar'); | ||||
|     spyExtractTar.mockImplementation(() => tempDir); | ||||
| 
 | ||||
|     infoSpy = jest.spyOn(core, 'info'); | ||||
|     infoSpy.mockImplementation(() => {}); | ||||
| 
 | ||||
|     warningSpy = jest.spyOn(core, 'warning'); | ||||
|     warningSpy.mockImplementation(() => null); | ||||
| 
 | ||||
|     debugSpy = jest.spyOn(core, 'debug'); | ||||
|     debugSpy.mockImplementation(() => null); | ||||
| 
 | ||||
|     spyFsReadDir = jest.spyOn(fs, 'readdirSync'); | ||||
|     spyFsReadDir.mockImplementation(() => ['GraalPyTest']); | ||||
| 
 | ||||
|     spyFsWriteFile = jest.spyOn(fs, 'writeFileSync'); | ||||
|     spyFsWriteFile.mockImplementation(() => undefined); | ||||
| 
 | ||||
|     spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); | ||||
|     spyHttpClient.mockImplementation( | ||||
|       async (): Promise<ifm.ITypedResponse<IGraalPyManifestRelease[]>> => { | ||||
|         const result = JSON.stringify(manifestData); | ||||
|         return { | ||||
|           statusCode: 200, | ||||
|           headers: {}, | ||||
|           result: JSON.parse(result) as IGraalPyManifestRelease[] | ||||
|         }; | ||||
|       } | ||||
|     ); | ||||
| 
 | ||||
|     spyExec = jest.spyOn(exec, 'exec'); | ||||
|     spyExec.mockImplementation(() => undefined); | ||||
| 
 | ||||
|     spySymlinkSync = jest.spyOn(fs, 'symlinkSync'); | ||||
|     spySymlinkSync.mockImplementation(() => undefined); | ||||
| 
 | ||||
|     spyExistsSync = jest.spyOn(fs, 'existsSync'); | ||||
|     spyExistsSync.mockImplementation(() => false); | ||||
|   }); | ||||
| 
 | ||||
|   afterEach(() => { | ||||
|     jest.resetAllMocks(); | ||||
|     jest.clearAllMocks(); | ||||
|     jest.restoreAllMocks(); | ||||
|   }); | ||||
| 
 | ||||
|   it('throw if release is not found', async () => { | ||||
|     await expect( | ||||
|       installer.installGraalPy('7.3.3', architecture, false, undefined) | ||||
|     ).rejects.toThrow( | ||||
|       `GraalPy version 7.3.3 with arch ${architecture} not found` | ||||
|     ); | ||||
| 
 | ||||
|     expect(spyHttpClient).toHaveBeenCalled(); | ||||
|     expect(spyDownloadTool).not.toHaveBeenCalled(); | ||||
|     expect(spyExec).not.toHaveBeenCalled(); | ||||
|   }); | ||||
| 
 | ||||
|   it('found and install GraalPy', async () => { | ||||
|     spyCacheDir = jest.spyOn(tc, 'cacheDir'); | ||||
|     spyCacheDir.mockImplementation(() => | ||||
|       path.join(toolDir, 'GraalPy', '21.3.0', architecture) | ||||
|     ); | ||||
| 
 | ||||
|     spyChmodSync = jest.spyOn(fs, 'chmodSync'); | ||||
|     spyChmodSync.mockImplementation(() => undefined); | ||||
| 
 | ||||
|     await expect( | ||||
|       installer.installGraalPy('21.x', architecture, false, undefined) | ||||
|     ).resolves.toEqual({ | ||||
|       installDir: path.join(toolDir, 'GraalPy', '21.3.0', architecture), | ||||
|       resolvedGraalPyVersion: '21.3.0' | ||||
|     }); | ||||
| 
 | ||||
|     expect(spyHttpClient).toHaveBeenCalled(); | ||||
|     expect(spyDownloadTool).toHaveBeenCalled(); | ||||
|     expect(spyCacheDir).toHaveBeenCalled(); | ||||
|     expect(spyExec).toHaveBeenCalled(); | ||||
|   }); | ||||
| 
 | ||||
|   it('found and install GraalPy, pre-release fallback', async () => { | ||||
|     spyCacheDir = jest.spyOn(tc, 'cacheDir'); | ||||
|     spyCacheDir.mockImplementation(() => | ||||
|       path.join(toolDir, 'GraalPy', '23.1.0', architecture) | ||||
|     ); | ||||
| 
 | ||||
|     spyChmodSync = jest.spyOn(fs, 'chmodSync'); | ||||
|     spyChmodSync.mockImplementation(() => undefined); | ||||
| 
 | ||||
|     await expect( | ||||
|       installer.installGraalPy('23.1.x', architecture, false, undefined) | ||||
|     ).rejects.toThrow(); | ||||
|     await expect( | ||||
|       installer.installGraalPy('23.1.x', architecture, true, undefined) | ||||
|     ).resolves.toEqual({ | ||||
|       installDir: path.join(toolDir, 'GraalPy', '23.1.0', architecture), | ||||
|       resolvedGraalPyVersion: '23.1.0-a.1' | ||||
|     }); | ||||
| 
 | ||||
|     expect(spyHttpClient).toHaveBeenCalled(); | ||||
|     expect(spyDownloadTool).toHaveBeenCalled(); | ||||
|     expect(spyCacheDir).toHaveBeenCalled(); | ||||
|     expect(spyExec).toHaveBeenCalled(); | ||||
|   }); | ||||
| }); | ||||
| @ -11,7 +11,8 @@ import { | ||||
|   isCacheFeatureAvailable, | ||||
|   getVersionInputFromFile, | ||||
|   getVersionInputFromPlainFile, | ||||
|   getVersionInputFromTomlFile | ||||
|   getVersionInputFromTomlFile, | ||||
|   getNextPageUrl | ||||
| } from '../src/utils'; | ||||
| 
 | ||||
| jest.mock('@actions/cache'); | ||||
| @ -136,3 +137,25 @@ describe('Version from file test', () => { | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
| 
 | ||||
| describe('getNextPageUrl', () => { | ||||
|   it('GitHub API pagination next page is parsed correctly', () => { | ||||
|     function generateResponse(link: string) { | ||||
|       return { | ||||
|         statusCode: 200, | ||||
|         result: null, | ||||
|         headers: { | ||||
|           link: link | ||||
|         } | ||||
|       }; | ||||
|     } | ||||
|     const page1Links = | ||||
|       '<https://api.github.com/repositories/129883600/releases?page=2>; rel="next", <https://api.github.com/repositories/129883600/releases?page=3>; rel="last"'; | ||||
|     expect(getNextPageUrl(generateResponse(page1Links))).toStrictEqual( | ||||
|       'https://api.github.com/repositories/129883600/releases?page=2' | ||||
|     ); | ||||
|     const page2Links = | ||||
|       '<https://api.github.com/repositories/129883600/releases?page=1>; rel="prev", <https://api.github.com/repositories/129883600/releases?page=1>; rel="first"'; | ||||
|     expect(getNextPageUrl(generateResponse(page2Links))).toBeNull(); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
							
								
								
									
										399
									
								
								dist/setup/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										399
									
								
								dist/setup/index.js
									
									
									
									
										vendored
									
									
								
							| @ -69091,6 +69091,132 @@ class PoetryCache extends cache_distributor_1.default { | ||||
| exports["default"] = PoetryCache; | ||||
| 
 | ||||
| 
 | ||||
| /***/ }), | ||||
| 
 | ||||
| /***/ 8040: | ||||
| /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| 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.prototype.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.parseGraalPyVersion = exports.findGraalPyToolCache = exports.findGraalPyVersion = void 0; | ||||
| const path = __importStar(__nccwpck_require__(1017)); | ||||
| const graalpyInstall = __importStar(__nccwpck_require__(8265)); | ||||
| 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 findGraalPyVersion(versionSpec, architecture, updateEnvironment, checkLatest, allowPreReleases) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         let resolvedGraalPyVersion = ''; | ||||
|         let installDir; | ||||
|         let releases; | ||||
|         let graalpyVersionSpec = parseGraalPyVersion(versionSpec); | ||||
|         if (checkLatest) { | ||||
|             releases = yield graalpyInstall.getAvailableGraalPyVersions(); | ||||
|             if (releases && releases.length > 0) { | ||||
|                 const releaseData = graalpyInstall.findRelease(releases, graalpyVersionSpec, architecture, false); | ||||
|                 if (releaseData) { | ||||
|                     core.info(`Resolved as GraalPy ${releaseData.resolvedGraalPyVersion}`); | ||||
|                     graalpyVersionSpec = releaseData.resolvedGraalPyVersion; | ||||
|                 } | ||||
|                 else { | ||||
|                     core.info(`Failed to resolve GraalPy ${graalpyVersionSpec} from manifest`); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         ({ installDir, resolvedGraalPyVersion } = findGraalPyToolCache(graalpyVersionSpec, architecture)); | ||||
|         if (!installDir) { | ||||
|             ({ installDir, resolvedGraalPyVersion } = yield graalpyInstall.installGraalPy(graalpyVersionSpec, architecture, allowPreReleases, releases)); | ||||
|         } | ||||
|         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 = utils_1.getBinaryDirectory(installDir); | ||||
|         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', 'graalpy' + resolvedGraalPyVersion); | ||||
|         core.setOutput('python-path', pythonPath); | ||||
|         return resolvedGraalPyVersion; | ||||
|     }); | ||||
| } | ||||
| exports.findGraalPyVersion = findGraalPyVersion; | ||||
| function findGraalPyToolCache(graalpyVersion, architecture) { | ||||
|     let resolvedGraalPyVersion = ''; | ||||
|     let installDir = tc.find('GraalPy', graalpyVersion, architecture); | ||||
|     if (installDir) { | ||||
|         // 'tc.find' finds tool based on Python version but we also need to check
 | ||||
|         // whether GraalPy version satisfies requested version.
 | ||||
|         resolvedGraalPyVersion = path.basename(path.dirname(installDir)); | ||||
|         const isGraalPyVersionSatisfies = semver.satisfies(resolvedGraalPyVersion, graalpyVersion); | ||||
|         if (!isGraalPyVersionSatisfies) { | ||||
|             installDir = null; | ||||
|             resolvedGraalPyVersion = ''; | ||||
|         } | ||||
|     } | ||||
|     if (!installDir) { | ||||
|         core.info(`GraalPy version ${graalpyVersion} was not found in the local cache`); | ||||
|     } | ||||
|     return { installDir, resolvedGraalPyVersion }; | ||||
| } | ||||
| exports.findGraalPyToolCache = findGraalPyToolCache; | ||||
| function parseGraalPyVersion(versionSpec) { | ||||
|     const versions = versionSpec.split('-').filter(item => !!item); | ||||
|     if (/^(graalpy)(.+)/.test(versions[0])) { | ||||
|         const version = versions[0].replace('graalpy', ''); | ||||
|         versions.splice(0, 1, 'graalpy', version); | ||||
|     } | ||||
|     if (versions.length < 2 || versions[0] != 'graalpy') { | ||||
|         throw new Error("Invalid 'version' property for GraalPy. GraalPy version should be specified as 'graalpy<python-version>' or 'graalpy-<python-version>'. See README for examples and documentation."); | ||||
|     } | ||||
|     const pythonVersion = versions[1]; | ||||
|     if (!utils_1.validateVersion(pythonVersion)) { | ||||
|         throw new Error("Invalid 'version' property for GraalPy. GraalPy versions should satisfy SemVer notation. See README for examples and documentation."); | ||||
|     } | ||||
|     return pythonVersion; | ||||
| } | ||||
| exports.parseGraalPyVersion = parseGraalPyVersion; | ||||
| 
 | ||||
| 
 | ||||
| /***/ }), | ||||
| 
 | ||||
| /***/ 4003: | ||||
| @ -69164,7 +69290,7 @@ function findPyPyVersion(versionSpec, architecture, updateEnvironment, checkLate | ||||
|         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); | ||||
|         const pythonLocation = utils_1.getBinaryDirectory(installDir); | ||||
|         if (updateEnvironment) { | ||||
|             core.exportVariable('pythonLocation', installDir); | ||||
|             // https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython
 | ||||
| @ -69419,6 +69545,222 @@ function pythonVersionToSemantic(versionSpec, allowPreReleases) { | ||||
| exports.pythonVersionToSemantic = pythonVersionToSemantic; | ||||
| 
 | ||||
| 
 | ||||
| /***/ }), | ||||
| 
 | ||||
| /***/ 8265: | ||||
| /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| 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.prototype.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()); | ||||
|     }); | ||||
| }; | ||||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||||
|     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", ({ value: true })); | ||||
| exports.findAsset = exports.toGraalPyArchitecture = exports.toGraalPyPlatform = exports.findRelease = exports.graalPyTagToVersion = exports.getAvailableGraalPyVersions = exports.installGraalPy = void 0; | ||||
| const os = __importStar(__nccwpck_require__(2037)); | ||||
| const path = __importStar(__nccwpck_require__(1017)); | ||||
| const core = __importStar(__nccwpck_require__(2186)); | ||||
| const tc = __importStar(__nccwpck_require__(7784)); | ||||
| const semver = __importStar(__nccwpck_require__(1383)); | ||||
| 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); | ||||
| const TOKEN = core.getInput('token'); | ||||
| const AUTH = !TOKEN ? undefined : `token ${TOKEN}`; | ||||
| function installGraalPy(graalpyVersion, architecture, allowPreReleases, releases) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         let downloadDir; | ||||
|         releases = releases !== null && releases !== void 0 ? releases : (yield getAvailableGraalPyVersions()); | ||||
|         if (!releases || !releases.length) { | ||||
|             throw new Error('No release was found in GraalPy version.json'); | ||||
|         } | ||||
|         let releaseData = findRelease(releases, graalpyVersion, architecture, false); | ||||
|         if (allowPreReleases && (!releaseData || !releaseData.foundAsset)) { | ||||
|             // check for pre-release
 | ||||
|             core.info([ | ||||
|                 `Stable GraalPy version ${graalpyVersion} with arch ${architecture} not found`, | ||||
|                 `Trying pre-release versions` | ||||
|             ].join(os.EOL)); | ||||
|             releaseData = findRelease(releases, graalpyVersion, architecture, true); | ||||
|         } | ||||
|         if (!releaseData || !releaseData.foundAsset) { | ||||
|             throw new Error(`GraalPy version ${graalpyVersion} with arch ${architecture} not found`); | ||||
|         } | ||||
|         const { foundAsset, resolvedGraalPyVersion } = releaseData; | ||||
|         const downloadUrl = `${foundAsset.browser_download_url}`; | ||||
|         core.info(`Downloading GraalPy from "${downloadUrl}" ...`); | ||||
|         try { | ||||
|             const graalpyPath = yield tc.downloadTool(downloadUrl, undefined, AUTH); | ||||
|             core.info('Extracting downloaded archive...'); | ||||
|             downloadDir = yield tc.extractTar(graalpyPath); | ||||
|             // root folder in archive can have unpredictable name so just take the first folder
 | ||||
|             // downloadDir is unique folder under TEMP and can't contain any other folders
 | ||||
|             const archiveName = fs_1.default.readdirSync(downloadDir)[0]; | ||||
|             const toolDir = path.join(downloadDir, archiveName); | ||||
|             let installDir = toolDir; | ||||
|             if (!utils_1.isNightlyKeyword(resolvedGraalPyVersion)) { | ||||
|                 installDir = yield tc.cacheDir(toolDir, 'GraalPy', resolvedGraalPyVersion, architecture); | ||||
|             } | ||||
|             const binaryPath = utils_1.getBinaryDirectory(installDir); | ||||
|             yield createGraalPySymlink(binaryPath, resolvedGraalPyVersion); | ||||
|             yield installPip(binaryPath); | ||||
|             return { installDir, resolvedGraalPyVersion }; | ||||
|         } | ||||
|         catch (err) { | ||||
|             if (err instanceof Error) { | ||||
|                 // Rate limit?
 | ||||
|                 if (err instanceof tc.HTTPError && | ||||
|                     (err.httpStatusCode === 403 || err.httpStatusCode === 429)) { | ||||
|                     core.info(`Received HTTP status code ${err.httpStatusCode}.  This usually indicates the rate limit has been exceeded`); | ||||
|                 } | ||||
|                 else { | ||||
|                     core.info(err.message); | ||||
|                 } | ||||
|                 if (err.stack !== undefined) { | ||||
|                     core.debug(err.stack); | ||||
|                 } | ||||
|             } | ||||
|             throw err; | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| exports.installGraalPy = installGraalPy; | ||||
| function getAvailableGraalPyVersions() { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const http = new httpm.HttpClient('tool-cache'); | ||||
|         const headers = {}; | ||||
|         if (AUTH) { | ||||
|             headers.authorization = AUTH; | ||||
|         } | ||||
|         let url = 'https://api.github.com/repos/oracle/graalpython/releases'; | ||||
|         const result = []; | ||||
|         do { | ||||
|             const response = yield http.getJson(url, headers); | ||||
|             if (!response.result) { | ||||
|                 throw new Error(`Unable to retrieve the list of available GraalPy versions from '${url}'`); | ||||
|             } | ||||
|             result.push(...response.result); | ||||
|             url = utils_1.getNextPageUrl(response); | ||||
|         } while (url); | ||||
|         return result; | ||||
|     }); | ||||
| } | ||||
| exports.getAvailableGraalPyVersions = getAvailableGraalPyVersions; | ||||
| function createGraalPySymlink(graalpyBinaryPath, graalpyVersion) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const version = semver.coerce(graalpyVersion); | ||||
|         const pythonBinaryPostfix = semver.major(version); | ||||
|         const pythonMinor = semver.minor(version); | ||||
|         const graalpyMajorMinorBinaryPostfix = `${pythonBinaryPostfix}.${pythonMinor}`; | ||||
|         const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : ''; | ||||
|         core.info('Creating symlinks...'); | ||||
|         utils_1.createSymlinkInFolder(graalpyBinaryPath, `graalpy${binaryExtension}`, `python${pythonBinaryPostfix}${binaryExtension}`, true); | ||||
|         utils_1.createSymlinkInFolder(graalpyBinaryPath, `graalpy${binaryExtension}`, `python${binaryExtension}`, true); | ||||
|         utils_1.createSymlinkInFolder(graalpyBinaryPath, `graalpy${binaryExtension}`, `graalpy${graalpyMajorMinorBinaryPostfix}${binaryExtension}`, true); | ||||
|     }); | ||||
| } | ||||
| function installPip(pythonLocation) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         core.info("Installing pip (GraalPy doesn't update pip because it uses a patched version of pip)"); | ||||
|         const pythonBinary = path.join(pythonLocation, 'python'); | ||||
|         yield exec.exec(`${pythonBinary} -m ensurepip --default-pip`); | ||||
|     }); | ||||
| } | ||||
| function graalPyTagToVersion(tag) { | ||||
|     const versionPattern = /.*-(\d+\.\d+\.\d+(?:\.\d+)?)((?:a|b|rc))?(\d*)?/; | ||||
|     const match = tag.match(versionPattern); | ||||
|     if (match && match[2]) { | ||||
|         return `${match[1]}-${match[2]}.${match[3]}`; | ||||
|     } | ||||
|     else if (match) { | ||||
|         return match[1]; | ||||
|     } | ||||
|     else { | ||||
|         return tag.replace(/.*-/, ''); | ||||
|     } | ||||
| } | ||||
| exports.graalPyTagToVersion = graalPyTagToVersion; | ||||
| function findRelease(releases, graalpyVersion, architecture, includePrerelease) { | ||||
|     const options = { includePrerelease: includePrerelease }; | ||||
|     const filterReleases = releases.filter(item => { | ||||
|         const isVersionSatisfied = semver.satisfies(graalPyTagToVersion(item.tag_name), graalpyVersion, options); | ||||
|         return (isVersionSatisfied && !!findAsset(item, architecture, process.platform)); | ||||
|     }); | ||||
|     if (!filterReleases.length) { | ||||
|         return null; | ||||
|     } | ||||
|     const sortedReleases = filterReleases.sort((previous, current) => semver.compare(semver.coerce(graalPyTagToVersion(current.tag_name)), semver.coerce(graalPyTagToVersion(previous.tag_name)))); | ||||
|     const foundRelease = sortedReleases[0]; | ||||
|     const foundAsset = findAsset(foundRelease, architecture, process.platform); | ||||
|     return { | ||||
|         foundAsset, | ||||
|         resolvedGraalPyVersion: graalPyTagToVersion(foundRelease.tag_name) | ||||
|     }; | ||||
| } | ||||
| exports.findRelease = findRelease; | ||||
| function toGraalPyPlatform(platform) { | ||||
|     switch (platform) { | ||||
|         case 'win32': | ||||
|             return 'windows'; | ||||
|         case 'darwin': | ||||
|             return 'macos'; | ||||
|     } | ||||
|     return platform; | ||||
| } | ||||
| exports.toGraalPyPlatform = toGraalPyPlatform; | ||||
| function toGraalPyArchitecture(architecture) { | ||||
|     switch (architecture) { | ||||
|         case 'x64': | ||||
|             return 'amd64'; | ||||
|         case 'arm64': | ||||
|             return 'aarch64'; | ||||
|     } | ||||
|     return architecture; | ||||
| } | ||||
| exports.toGraalPyArchitecture = toGraalPyArchitecture; | ||||
| function findAsset(item, architecture, platform) { | ||||
|     const graalpyArch = toGraalPyArchitecture(architecture); | ||||
|     const graalpyPlatform = toGraalPyPlatform(platform); | ||||
|     const found = item.assets.filter(file => file.name.startsWith('graalpy') && | ||||
|         file.name.endsWith(`-${graalpyPlatform}-${graalpyArch}.tar.gz`)); | ||||
|     /* | ||||
|     In the future there could be more variants of GraalPy for a single release. Pick the shortest name, that one is the most likely to be the primary variant. | ||||
|     */ | ||||
|     found.sort((f1, f2) => f1.name.length - f2.name.length); | ||||
|     return found[0]; | ||||
| } | ||||
| exports.findAsset = findAsset; | ||||
| 
 | ||||
| 
 | ||||
| /***/ }), | ||||
| 
 | ||||
| /***/ 8168: | ||||
| @ -69458,7 +69800,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.findRelease = exports.getAvailablePyPyVersions = exports.installPyPy = void 0; | ||||
| const os = __importStar(__nccwpck_require__(2037)); | ||||
| const path = __importStar(__nccwpck_require__(1017)); | ||||
| const core = __importStar(__nccwpck_require__(2186)); | ||||
| @ -69508,7 +69850,7 @@ function installPyPy(pypyVersion, pythonVersion, architecture, allowPreReleases, | ||||
|                 installDir = yield tc.cacheDir(toolDir, 'PyPy', resolvedPythonVersion, architecture); | ||||
|             } | ||||
|             utils_1.writeExactPyPyVersionFile(installDir, resolvedPyPyVersion); | ||||
|             const binaryPath = getPyPyBinaryPath(installDir); | ||||
|             const binaryPath = utils_1.getBinaryDirectory(installDir); | ||||
|             yield createPyPySymlink(binaryPath, resolvedPythonVersion); | ||||
|             yield installPip(binaryPath); | ||||
|             return { installDir, resolvedPythonVersion, resolvedPyPyVersion }; | ||||
| @ -69597,15 +69939,6 @@ function findRelease(releases, pythonVersion, pypyVersion, architecture, include | ||||
|     }; | ||||
| } | ||||
| exports.findRelease = findRelease; | ||||
| /** Get PyPy binary location from the tool of installation directory | ||||
|  *  - On Linux and macOS, the Python interpreter is in 'bin'. | ||||
|  *  - On Windows, it is in the installation root. | ||||
|  */ | ||||
| function getPyPyBinaryPath(installDir) { | ||||
|     const _binDir = path.join(installDir, 'bin'); | ||||
|     return utils_1.IS_WINDOWS ? installDir : _binDir; | ||||
| } | ||||
| exports.getPyPyBinaryPath = getPyPyBinaryPath; | ||||
| function pypyVersionToSemantic(versionSpec) { | ||||
|     const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc))(\d*)/g; | ||||
|     return versionSpec.replace(prereleaseVersion, '$1-$2.$3'); | ||||
| @ -69804,6 +70137,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); | ||||
| const core = __importStar(__nccwpck_require__(2186)); | ||||
| const finder = __importStar(__nccwpck_require__(9996)); | ||||
| const finderPyPy = __importStar(__nccwpck_require__(4003)); | ||||
| const finderGraalPy = __importStar(__nccwpck_require__(8040)); | ||||
| const path = __importStar(__nccwpck_require__(1017)); | ||||
| const os = __importStar(__nccwpck_require__(2037)); | ||||
| const fs_1 = __importDefault(__nccwpck_require__(7147)); | ||||
| @ -69812,6 +70146,9 @@ const utils_1 = __nccwpck_require__(1314); | ||||
| function isPyPyVersion(versionSpec) { | ||||
|     return versionSpec.startsWith('pypy'); | ||||
| } | ||||
| function isGraalPyVersion(versionSpec) { | ||||
|     return versionSpec.startsWith('graalpy'); | ||||
| } | ||||
| function cacheDependencies(cache, pythonVersion) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const cacheDependencyPath = core.getInput('cache-dependency-path') || undefined; | ||||
| @ -69880,6 +70217,11 @@ function run() { | ||||
|                         pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`; | ||||
|                         core.info(`Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`); | ||||
|                     } | ||||
|                     else if (isGraalPyVersion(version)) { | ||||
|                         const installed = yield finderGraalPy.findGraalPyVersion(version, arch, updateEnvironment, checkLatest, allowPreReleases); | ||||
|                         pythonVersion = `${installed}`; | ||||
|                         core.info(`Successfully set up GraalPy ${installed}`); | ||||
|                     } | ||||
|                     else { | ||||
|                         if (version.startsWith('2')) { | ||||
|                             core.warning('The support for python 2.7 will be removed on June 19. Related issue: https://github.com/actions/setup-python/issues/672'); | ||||
| @ -69948,7 +70290,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { | ||||
|     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", ({ value: true })); | ||||
| exports.getVersionInputFromFile = exports.getVersionInputFromPlainFile = exports.getVersionInputFromTomlFile = exports.getOSInfo = exports.getLinuxInfo = exports.logWarning = exports.isCacheFeatureAvailable = exports.isGhes = exports.validatePythonVersionFormatForPyPy = exports.writeExactPyPyVersionFile = exports.readExactPyPyVersionFile = exports.getPyPyVersionFromPath = exports.isNightlyKeyword = exports.validateVersion = exports.createSymlinkInFolder = exports.WINDOWS_PLATFORMS = exports.WINDOWS_ARCHS = exports.IS_MAC = exports.IS_LINUX = exports.IS_WINDOWS = void 0; | ||||
| exports.getNextPageUrl = exports.getBinaryDirectory = exports.getVersionInputFromFile = exports.getVersionInputFromPlainFile = exports.getVersionInputFromTomlFile = exports.getOSInfo = exports.getLinuxInfo = exports.logWarning = exports.isCacheFeatureAvailable = exports.isGhes = exports.validatePythonVersionFormatForPyPy = exports.writeExactPyPyVersionFile = exports.readExactPyPyVersionFile = exports.getPyPyVersionFromPath = exports.isNightlyKeyword = exports.validateVersion = exports.createSymlinkInFolder = exports.WINDOWS_PLATFORMS = exports.WINDOWS_ARCHS = exports.IS_MAC = exports.IS_LINUX = exports.IS_WINDOWS = void 0; | ||||
| /* eslint no-unsafe-finally: "off" */ | ||||
| const cache = __importStar(__nccwpck_require__(7799)); | ||||
| const core = __importStar(__nccwpck_require__(2186)); | ||||
| @ -70177,6 +70519,37 @@ function getVersionInputFromFile(versionFile) { | ||||
|     } | ||||
| } | ||||
| exports.getVersionInputFromFile = getVersionInputFromFile; | ||||
| /** | ||||
|  * Get the directory containing interpreter binary from installation directory of PyPy or GraalPy | ||||
|  *  - On Linux and macOS, the Python interpreter is in 'bin'. | ||||
|  *  - On Windows, it is in the installation root. | ||||
|  */ | ||||
| function getBinaryDirectory(installDir) { | ||||
|     return exports.IS_WINDOWS ? installDir : path.join(installDir, 'bin'); | ||||
| } | ||||
| exports.getBinaryDirectory = getBinaryDirectory; | ||||
| /** | ||||
|  * Extract next page URL from a HTTP response "link" header. Such headers are used in GitHub APIs. | ||||
|  */ | ||||
| function getNextPageUrl(response) { | ||||
|     const responseHeaders = response.headers; | ||||
|     const linkHeader = responseHeaders.link; | ||||
|     if (typeof linkHeader === 'string') { | ||||
|         for (const link of linkHeader.split(/\s*,\s*/)) { | ||||
|             const match = link.match(/<([^>]+)>(.*)/); | ||||
|             if (match) { | ||||
|                 const url = match[1]; | ||||
|                 for (const param of match[2].split(/\s*;\s*/)) { | ||||
|                     if (param.match(/rel="?next"?/)) { | ||||
|                         return url; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return null; | ||||
| } | ||||
| exports.getNextPageUrl = getNextPageUrl; | ||||
| 
 | ||||
| 
 | ||||
| /***/ }), | ||||
|  | ||||
							
								
								
									
										146
									
								
								src/find-graalpy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								src/find-graalpy.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,146 @@ | ||||
| import * as path from 'path'; | ||||
| import * as graalpyInstall from './install-graalpy'; | ||||
| import { | ||||
|   IS_WINDOWS, | ||||
|   validateVersion, | ||||
|   IGraalPyManifestRelease, | ||||
|   getBinaryDirectory | ||||
| } from './utils'; | ||||
| 
 | ||||
| import * as semver from 'semver'; | ||||
| import * as core from '@actions/core'; | ||||
| import * as tc from '@actions/tool-cache'; | ||||
| 
 | ||||
| export async function findGraalPyVersion( | ||||
|   versionSpec: string, | ||||
|   architecture: string, | ||||
|   updateEnvironment: boolean, | ||||
|   checkLatest: boolean, | ||||
|   allowPreReleases: boolean | ||||
| ): Promise<string> { | ||||
|   let resolvedGraalPyVersion = ''; | ||||
|   let installDir: string | null; | ||||
|   let releases: IGraalPyManifestRelease[] | undefined; | ||||
| 
 | ||||
|   let graalpyVersionSpec = parseGraalPyVersion(versionSpec); | ||||
| 
 | ||||
|   if (checkLatest) { | ||||
|     releases = await graalpyInstall.getAvailableGraalPyVersions(); | ||||
|     if (releases && releases.length > 0) { | ||||
|       const releaseData = graalpyInstall.findRelease( | ||||
|         releases, | ||||
|         graalpyVersionSpec, | ||||
|         architecture, | ||||
|         false | ||||
|       ); | ||||
| 
 | ||||
|       if (releaseData) { | ||||
|         core.info(`Resolved as GraalPy ${releaseData.resolvedGraalPyVersion}`); | ||||
|         graalpyVersionSpec = releaseData.resolvedGraalPyVersion; | ||||
|       } else { | ||||
|         core.info( | ||||
|           `Failed to resolve GraalPy ${graalpyVersionSpec} from manifest` | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   ({installDir, resolvedGraalPyVersion} = findGraalPyToolCache( | ||||
|     graalpyVersionSpec, | ||||
|     architecture | ||||
|   )); | ||||
| 
 | ||||
|   if (!installDir) { | ||||
|     ({installDir, resolvedGraalPyVersion} = await graalpyInstall.installGraalPy( | ||||
|       graalpyVersionSpec, | ||||
|       architecture, | ||||
|       allowPreReleases, | ||||
|       releases | ||||
|     )); | ||||
|   } | ||||
| 
 | ||||
|   const pipDir = IS_WINDOWS ? 'Scripts' : 'bin'; | ||||
|   const _binDir = path.join(installDir, pipDir); | ||||
|   const binaryExtension = IS_WINDOWS ? '.exe' : ''; | ||||
|   const pythonPath = path.join( | ||||
|     IS_WINDOWS ? installDir : _binDir, | ||||
|     `python${binaryExtension}` | ||||
|   ); | ||||
|   const pythonLocation = getBinaryDirectory(installDir); | ||||
|   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', 'graalpy' + resolvedGraalPyVersion); | ||||
|   core.setOutput('python-path', pythonPath); | ||||
| 
 | ||||
|   return resolvedGraalPyVersion; | ||||
| } | ||||
| 
 | ||||
| export function findGraalPyToolCache( | ||||
|   graalpyVersion: string, | ||||
|   architecture: string | ||||
| ) { | ||||
|   let resolvedGraalPyVersion = ''; | ||||
|   let installDir: string | null = tc.find( | ||||
|     'GraalPy', | ||||
|     graalpyVersion, | ||||
|     architecture | ||||
|   ); | ||||
| 
 | ||||
|   if (installDir) { | ||||
|     // 'tc.find' finds tool based on Python version but we also need to check
 | ||||
|     // whether GraalPy version satisfies requested version.
 | ||||
|     resolvedGraalPyVersion = path.basename(path.dirname(installDir)); | ||||
| 
 | ||||
|     const isGraalPyVersionSatisfies = semver.satisfies( | ||||
|       resolvedGraalPyVersion, | ||||
|       graalpyVersion | ||||
|     ); | ||||
|     if (!isGraalPyVersionSatisfies) { | ||||
|       installDir = null; | ||||
|       resolvedGraalPyVersion = ''; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (!installDir) { | ||||
|     core.info( | ||||
|       `GraalPy version ${graalpyVersion} was not found in the local cache` | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   return {installDir, resolvedGraalPyVersion}; | ||||
| } | ||||
| 
 | ||||
| export function parseGraalPyVersion(versionSpec: string): string { | ||||
|   const versions = versionSpec.split('-').filter(item => !!item); | ||||
| 
 | ||||
|   if (/^(graalpy)(.+)/.test(versions[0])) { | ||||
|     const version = versions[0].replace('graalpy', ''); | ||||
|     versions.splice(0, 1, 'graalpy', version); | ||||
|   } | ||||
| 
 | ||||
|   if (versions.length < 2 || versions[0] != 'graalpy') { | ||||
|     throw new Error( | ||||
|       "Invalid 'version' property for GraalPy. GraalPy version should be specified as 'graalpy<python-version>' or 'graalpy-<python-version>'. See README for examples and documentation." | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   const pythonVersion = versions[1]; | ||||
| 
 | ||||
|   if (!validateVersion(pythonVersion)) { | ||||
|     throw new Error( | ||||
|       "Invalid 'version' property for GraalPy. GraalPy versions should satisfy SemVer notation. See README for examples and documentation." | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   return pythonVersion; | ||||
| } | ||||
| @ -7,7 +7,8 @@ import { | ||||
|   getPyPyVersionFromPath, | ||||
|   readExactPyPyVersionFile, | ||||
|   validatePythonVersionFormatForPyPy, | ||||
|   IPyPyManifestRelease | ||||
|   IPyPyManifestRelease, | ||||
|   getBinaryDirectory | ||||
| } from './utils'; | ||||
| 
 | ||||
| import * as semver from 'semver'; | ||||
| @ -82,7 +83,7 @@ export async function findPyPyVersion( | ||||
|     IS_WINDOWS ? installDir : _binDir, | ||||
|     `python${binaryExtension}` | ||||
|   ); | ||||
|   const pythonLocation = pypyInstall.getPyPyBinaryPath(installDir); | ||||
|   const pythonLocation = getBinaryDirectory(installDir); | ||||
|   if (updateEnvironment) { | ||||
|     core.exportVariable('pythonLocation', installDir); | ||||
|     // https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython
 | ||||
|  | ||||
							
								
								
									
										262
									
								
								src/install-graalpy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								src/install-graalpy.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,262 @@ | ||||
| import * as os from 'os'; | ||||
| import * as path from 'path'; | ||||
| import * as core from '@actions/core'; | ||||
| import * as tc from '@actions/tool-cache'; | ||||
| import * as semver from 'semver'; | ||||
| import * as httpm from '@actions/http-client'; | ||||
| import * as ifm from '@actions/http-client/interfaces'; | ||||
| import * as exec from '@actions/exec'; | ||||
| import fs from 'fs'; | ||||
| 
 | ||||
| import { | ||||
|   IS_WINDOWS, | ||||
|   IGraalPyManifestRelease, | ||||
|   createSymlinkInFolder, | ||||
|   isNightlyKeyword, | ||||
|   getBinaryDirectory, | ||||
|   getNextPageUrl | ||||
| } from './utils'; | ||||
| 
 | ||||
| const TOKEN = core.getInput('token'); | ||||
| const AUTH = !TOKEN ? undefined : `token ${TOKEN}`; | ||||
| 
 | ||||
| export async function installGraalPy( | ||||
|   graalpyVersion: string, | ||||
|   architecture: string, | ||||
|   allowPreReleases: boolean, | ||||
|   releases: IGraalPyManifestRelease[] | undefined | ||||
| ) { | ||||
|   let downloadDir; | ||||
| 
 | ||||
|   releases = releases ?? (await getAvailableGraalPyVersions()); | ||||
| 
 | ||||
|   if (!releases || !releases.length) { | ||||
|     throw new Error('No release was found in GraalPy version.json'); | ||||
|   } | ||||
| 
 | ||||
|   let releaseData = findRelease(releases, graalpyVersion, architecture, false); | ||||
| 
 | ||||
|   if (allowPreReleases && (!releaseData || !releaseData.foundAsset)) { | ||||
|     // check for pre-release
 | ||||
|     core.info( | ||||
|       [ | ||||
|         `Stable GraalPy version ${graalpyVersion} with arch ${architecture} not found`, | ||||
|         `Trying pre-release versions` | ||||
|       ].join(os.EOL) | ||||
|     ); | ||||
|     releaseData = findRelease(releases, graalpyVersion, architecture, true); | ||||
|   } | ||||
| 
 | ||||
|   if (!releaseData || !releaseData.foundAsset) { | ||||
|     throw new Error( | ||||
|       `GraalPy version ${graalpyVersion} with arch ${architecture} not found` | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   const {foundAsset, resolvedGraalPyVersion} = releaseData; | ||||
|   const downloadUrl = `${foundAsset.browser_download_url}`; | ||||
| 
 | ||||
|   core.info(`Downloading GraalPy from "${downloadUrl}" ...`); | ||||
| 
 | ||||
|   try { | ||||
|     const graalpyPath = await tc.downloadTool(downloadUrl, undefined, AUTH); | ||||
| 
 | ||||
|     core.info('Extracting downloaded archive...'); | ||||
|     downloadDir = await tc.extractTar(graalpyPath); | ||||
| 
 | ||||
|     // root folder in archive can have unpredictable name so just take the first folder
 | ||||
|     // downloadDir is unique folder under TEMP and can't contain any other folders
 | ||||
|     const archiveName = fs.readdirSync(downloadDir)[0]; | ||||
| 
 | ||||
|     const toolDir = path.join(downloadDir, archiveName); | ||||
|     let installDir = toolDir; | ||||
|     if (!isNightlyKeyword(resolvedGraalPyVersion)) { | ||||
|       installDir = await tc.cacheDir( | ||||
|         toolDir, | ||||
|         'GraalPy', | ||||
|         resolvedGraalPyVersion, | ||||
|         architecture | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     const binaryPath = getBinaryDirectory(installDir); | ||||
|     await createGraalPySymlink(binaryPath, resolvedGraalPyVersion); | ||||
|     await installPip(binaryPath); | ||||
| 
 | ||||
|     return {installDir, resolvedGraalPyVersion}; | ||||
|   } catch (err) { | ||||
|     if (err instanceof Error) { | ||||
|       // Rate limit?
 | ||||
|       if ( | ||||
|         err instanceof tc.HTTPError && | ||||
|         (err.httpStatusCode === 403 || err.httpStatusCode === 429) | ||||
|       ) { | ||||
|         core.info( | ||||
|           `Received HTTP status code ${err.httpStatusCode}.  This usually indicates the rate limit has been exceeded` | ||||
|         ); | ||||
|       } else { | ||||
|         core.info(err.message); | ||||
|       } | ||||
|       if (err.stack !== undefined) { | ||||
|         core.debug(err.stack); | ||||
|       } | ||||
|     } | ||||
|     throw err; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export async function getAvailableGraalPyVersions() { | ||||
|   const http: httpm.HttpClient = new httpm.HttpClient('tool-cache'); | ||||
| 
 | ||||
|   const headers: ifm.IHeaders = {}; | ||||
|   if (AUTH) { | ||||
|     headers.authorization = AUTH; | ||||
|   } | ||||
| 
 | ||||
|   let url: string | null = | ||||
|     'https://api.github.com/repos/oracle/graalpython/releases'; | ||||
|   const result: IGraalPyManifestRelease[] = []; | ||||
|   do { | ||||
|     const response: ifm.ITypedResponse<IGraalPyManifestRelease[]> = | ||||
|       await http.getJson(url, headers); | ||||
|     if (!response.result) { | ||||
|       throw new Error( | ||||
|         `Unable to retrieve the list of available GraalPy versions from '${url}'` | ||||
|       ); | ||||
|     } | ||||
|     result.push(...response.result); | ||||
|     url = getNextPageUrl(response); | ||||
|   } while (url); | ||||
| 
 | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| async function createGraalPySymlink( | ||||
|   graalpyBinaryPath: string, | ||||
|   graalpyVersion: string | ||||
| ) { | ||||
|   const version = semver.coerce(graalpyVersion)!; | ||||
|   const pythonBinaryPostfix = semver.major(version); | ||||
|   const pythonMinor = semver.minor(version); | ||||
|   const graalpyMajorMinorBinaryPostfix = `${pythonBinaryPostfix}.${pythonMinor}`; | ||||
|   const binaryExtension = IS_WINDOWS ? '.exe' : ''; | ||||
| 
 | ||||
|   core.info('Creating symlinks...'); | ||||
|   createSymlinkInFolder( | ||||
|     graalpyBinaryPath, | ||||
|     `graalpy${binaryExtension}`, | ||||
|     `python${pythonBinaryPostfix}${binaryExtension}`, | ||||
|     true | ||||
|   ); | ||||
| 
 | ||||
|   createSymlinkInFolder( | ||||
|     graalpyBinaryPath, | ||||
|     `graalpy${binaryExtension}`, | ||||
|     `python${binaryExtension}`, | ||||
|     true | ||||
|   ); | ||||
| 
 | ||||
|   createSymlinkInFolder( | ||||
|     graalpyBinaryPath, | ||||
|     `graalpy${binaryExtension}`, | ||||
|     `graalpy${graalpyMajorMinorBinaryPostfix}${binaryExtension}`, | ||||
|     true | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| async function installPip(pythonLocation: string) { | ||||
|   core.info( | ||||
|     "Installing pip (GraalPy doesn't update pip because it uses a patched version of pip)" | ||||
|   ); | ||||
|   const pythonBinary = path.join(pythonLocation, 'python'); | ||||
|   await exec.exec(`${pythonBinary} -m ensurepip --default-pip`); | ||||
| } | ||||
| 
 | ||||
| export function graalPyTagToVersion(tag: string) { | ||||
|   const versionPattern = /.*-(\d+\.\d+\.\d+(?:\.\d+)?)((?:a|b|rc))?(\d*)?/; | ||||
|   const match = tag.match(versionPattern); | ||||
|   if (match && match[2]) { | ||||
|     return `${match[1]}-${match[2]}.${match[3]}`; | ||||
|   } else if (match) { | ||||
|     return match[1]; | ||||
|   } else { | ||||
|     return tag.replace(/.*-/, ''); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function findRelease( | ||||
|   releases: IGraalPyManifestRelease[], | ||||
|   graalpyVersion: string, | ||||
|   architecture: string, | ||||
|   includePrerelease: boolean | ||||
| ) { | ||||
|   const options = {includePrerelease: includePrerelease}; | ||||
|   const filterReleases = releases.filter(item => { | ||||
|     const isVersionSatisfied = semver.satisfies( | ||||
|       graalPyTagToVersion(item.tag_name), | ||||
|       graalpyVersion, | ||||
|       options | ||||
|     ); | ||||
|     return ( | ||||
|       isVersionSatisfied && !!findAsset(item, architecture, process.platform) | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   if (!filterReleases.length) { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   const sortedReleases = filterReleases.sort((previous, current) => | ||||
|     semver.compare( | ||||
|       semver.coerce(graalPyTagToVersion(current.tag_name))!, | ||||
|       semver.coerce(graalPyTagToVersion(previous.tag_name))! | ||||
|     ) | ||||
|   ); | ||||
| 
 | ||||
|   const foundRelease = sortedReleases[0]; | ||||
|   const foundAsset = findAsset(foundRelease, architecture, process.platform); | ||||
| 
 | ||||
|   return { | ||||
|     foundAsset, | ||||
|     resolvedGraalPyVersion: graalPyTagToVersion(foundRelease.tag_name) | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function toGraalPyPlatform(platform: string) { | ||||
|   switch (platform) { | ||||
|     case 'win32': | ||||
|       return 'windows'; | ||||
|     case 'darwin': | ||||
|       return 'macos'; | ||||
|   } | ||||
|   return platform; | ||||
| } | ||||
| 
 | ||||
| export function toGraalPyArchitecture(architecture: string) { | ||||
|   switch (architecture) { | ||||
|     case 'x64': | ||||
|       return 'amd64'; | ||||
|     case 'arm64': | ||||
|       return 'aarch64'; | ||||
|   } | ||||
|   return architecture; | ||||
| } | ||||
| 
 | ||||
| export function findAsset( | ||||
|   item: IGraalPyManifestRelease, | ||||
|   architecture: string, | ||||
|   platform: string | ||||
| ) { | ||||
|   const graalpyArch = toGraalPyArchitecture(architecture); | ||||
|   const graalpyPlatform = toGraalPyPlatform(platform); | ||||
|   const found = item.assets.filter( | ||||
|     file => | ||||
|       file.name.startsWith('graalpy') && | ||||
|       file.name.endsWith(`-${graalpyPlatform}-${graalpyArch}.tar.gz`) | ||||
|   ); | ||||
|   /* | ||||
|   In the future there could be more variants of GraalPy for a single release. Pick the shortest name, that one is the most likely to be the primary variant. | ||||
|   */ | ||||
|   found.sort((f1, f2) => f1.name.length - f2.name.length); | ||||
|   return found[0]; | ||||
| } | ||||
| @ -13,7 +13,8 @@ import { | ||||
|   IPyPyManifestRelease, | ||||
|   createSymlinkInFolder, | ||||
|   isNightlyKeyword, | ||||
|   writeExactPyPyVersionFile | ||||
|   writeExactPyPyVersionFile, | ||||
|   getBinaryDirectory | ||||
| } from './utils'; | ||||
| 
 | ||||
| export async function installPyPy( | ||||
| @ -94,7 +95,7 @@ export async function installPyPy( | ||||
| 
 | ||||
|     writeExactPyPyVersionFile(installDir, resolvedPyPyVersion); | ||||
| 
 | ||||
|     const binaryPath = getPyPyBinaryPath(installDir); | ||||
|     const binaryPath = getBinaryDirectory(installDir); | ||||
|     await createPyPySymlink(binaryPath, resolvedPythonVersion); | ||||
|     await installPip(binaryPath); | ||||
| 
 | ||||
| @ -237,15 +238,6 @@ export function findRelease( | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| /** Get PyPy binary location from the tool of installation directory | ||||
|  *  - On Linux and macOS, the Python interpreter is in 'bin'. | ||||
|  *  - On Windows, it is in the installation root. | ||||
|  */ | ||||
| export function getPyPyBinaryPath(installDir: string) { | ||||
|   const _binDir = path.join(installDir, 'bin'); | ||||
|   return IS_WINDOWS ? installDir : _binDir; | ||||
| } | ||||
| 
 | ||||
| export function pypyVersionToSemantic(versionSpec: string) { | ||||
|   const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc))(\d*)/g; | ||||
|   return versionSpec.replace(prereleaseVersion, '$1-$2.$3'); | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import * as core from '@actions/core'; | ||||
| import * as finder from './find-python'; | ||||
| import * as finderPyPy from './find-pypy'; | ||||
| import * as finderGraalPy from './find-graalpy'; | ||||
| import * as path from 'path'; | ||||
| import * as os from 'os'; | ||||
| import fs from 'fs'; | ||||
| @ -17,6 +18,10 @@ function isPyPyVersion(versionSpec: string) { | ||||
|   return versionSpec.startsWith('pypy'); | ||||
| } | ||||
| 
 | ||||
| function isGraalPyVersion(versionSpec: string) { | ||||
|   return versionSpec.startsWith('graalpy'); | ||||
| } | ||||
| 
 | ||||
| async function cacheDependencies(cache: string, pythonVersion: string) { | ||||
|   const cacheDependencyPath = | ||||
|     core.getInput('cache-dependency-path') || undefined; | ||||
| @ -106,6 +111,16 @@ async function run() { | ||||
|           core.info( | ||||
|             `Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})` | ||||
|           ); | ||||
|         } else if (isGraalPyVersion(version)) { | ||||
|           const installed = await finderGraalPy.findGraalPyVersion( | ||||
|             version, | ||||
|             arch, | ||||
|             updateEnvironment, | ||||
|             checkLatest, | ||||
|             allowPreReleases | ||||
|           ); | ||||
|           pythonVersion = `${installed}`; | ||||
|           core.info(`Successfully set up GraalPy ${installed}`); | ||||
|         } else { | ||||
|           if (version.startsWith('2')) { | ||||
|             core.warning( | ||||
|  | ||||
							
								
								
									
										42
									
								
								src/utils.ts
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								src/utils.ts
									
									
									
									
									
								
							| @ -6,6 +6,7 @@ import * as path from 'path'; | ||||
| import * as semver from 'semver'; | ||||
| import * as toml from '@iarna/toml'; | ||||
| import * as exec from '@actions/exec'; | ||||
| import * as ifm from '@actions/http-client/interfaces'; | ||||
| 
 | ||||
| export const IS_WINDOWS = process.platform === 'win32'; | ||||
| export const IS_LINUX = process.platform === 'linux'; | ||||
| @ -29,6 +30,16 @@ export interface IPyPyManifestRelease { | ||||
|   files: IPyPyManifestAsset[]; | ||||
| } | ||||
| 
 | ||||
| export interface IGraalPyManifestAsset { | ||||
|   name: string; | ||||
|   browser_download_url: string; | ||||
| } | ||||
| 
 | ||||
| export interface IGraalPyManifestRelease { | ||||
|   tag_name: string; | ||||
|   assets: IGraalPyManifestAsset[]; | ||||
| } | ||||
| 
 | ||||
| /** create Symlinks for downloaded PyPy | ||||
|  *  It should be executed only for downloaded versions in runtime, because | ||||
|  *  toolcache versions have this setup. | ||||
| @ -266,3 +277,34 @@ export function getVersionInputFromFile(versionFile: string): string[] { | ||||
|     return getVersionInputFromPlainFile(versionFile); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Get the directory containing interpreter binary from installation directory of PyPy or GraalPy | ||||
|  *  - On Linux and macOS, the Python interpreter is in 'bin'. | ||||
|  *  - On Windows, it is in the installation root. | ||||
|  */ | ||||
| export function getBinaryDirectory(installDir: string) { | ||||
|   return IS_WINDOWS ? installDir : path.join(installDir, 'bin'); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Extract next page URL from a HTTP response "link" header. Such headers are used in GitHub APIs. | ||||
|  */ | ||||
| export function getNextPageUrl<T>(response: ifm.ITypedResponse<T>) { | ||||
|   const responseHeaders = <ifm.IHeaders>response.headers; | ||||
|   const linkHeader = responseHeaders.link; | ||||
|   if (typeof linkHeader === 'string') { | ||||
|     for (const link of linkHeader.split(/\s*,\s*/)) { | ||||
|       const match = link.match(/<([^>]+)>(.*)/); | ||||
|       if (match) { | ||||
|         const url = match[1]; | ||||
|         for (const param of match[2].split(/\s*;\s*/)) { | ||||
|           if (param.match(/rel="?next"?/)) { | ||||
|             return url; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   return null; | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Tim Felgentreff
						Tim Felgentreff