Compare commits

..

1 Commits

Author SHA1 Message Date
Sascha Mann
e7e85c4fe5 Add production dependencies & build 2022-05-21 21:54:14 +02:00
38 changed files with 21441 additions and 59915 deletions

View File

@@ -3,6 +3,7 @@ name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: "[BUG] " title: "[BUG] "
labels: bug labels: bug
assignees: SaschaMann
--- ---

View File

@@ -3,32 +3,5 @@ updates:
- package-ecosystem: gitsubmodule - package-ecosystem: gitsubmodule
directory: "/" directory: "/"
schedule: schedule:
interval: monthly interval: daily
open-pull-requests-limit: 99 open-pull-requests-limit: 10
groups:
# Group all Git submodules PRs into a single PR:
all-gitsubmodule-actions:
patterns:
- "*"
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'monthly'
open-pull-requests-limit: 99
groups:
# Group all GitHub Actions PRs into a single PR:
all-github-actions:
patterns:
- "*"
- package-ecosystem: npm
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 99
groups:
# Group all NPM PRs into a single PR:
all-npm-actions:
patterns:
- "*"

View File

@@ -1,11 +0,0 @@
if !occursin("hostedtoolcache", Sys.BINDIR)
error("the wrong julia is being used: $(Sys.BINDIR)")
end
if VERSION >= v"1.7.0" # pkgdir was introduced here, and before then mtime wasn't a problem so just skip
using Pkg
src = pkgdir(Pkg, "src", "Pkg.jl")
# mtime is when it's compressed, ctime is when the file is extracted
if mtime(src) >= ctime(src)
error("source mtime ($(mtime(src))) is not earlier than ctime ($(ctime(src)))")
end
end

33
.github/workflows/backup.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Backup
on:
schedule:
- cron: '5 4 * * 0'
workflow_dispatch:
jobs:
backup:
runs-on: ubuntu-20.04
steps:
- name: Configure cache
uses: actions/cache@v2
with:
path: |
${{ env.GITHUB_WORKSPACE }}
~/.cache/restic
key: ${{ runner.os }}
- name: Install the correct Python version
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Run backup action
uses: julia-actions/restic-action@main
env: # Options: https://restic.readthedocs.io/en/latest/040_backup.html#environment-variables
RESTIC_REPOSITORY: b2:${{ secrets.B2_BUCKET }}:${{ github.repository }}
RESTIC_PASSWORD: ${{ secrets.RESTIC_PASSWORD }}
B2_ACCOUNT_ID: ${{ secrets.B2_ACCOUNT_ID }}
B2_ACCOUNT_KEY: ${{ secrets.B2_ACCOUNT_KEY }}

25
.github/workflows/checkin.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: "PR Checks"
on: [pull_request, push]
jobs:
check_pr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: "npm ci"
run: npm ci
- name: "npm run build"
run: npm run build
- name: "npm run test"
run: npm run test
- name: "check for uncommitted changes"
# Ensure no changes, but ignore node_modules dir since dev/fresh ci deps installed.
run: |
git diff --exit-code --stat -- . ':!node_modules' \
|| (echo "##[error] found changed files after build. please 'npm run build && npm run format'" \
"and check in all changes" \
&& exit 1)

View File

@@ -11,12 +11,6 @@
# #
name: "CodeQL" name: "CodeQL"
concurrency:
# Skip intermediate builds: all builds except for builds on the `master`, `main`, or `release-*` branches
# Cancel intermediate builds: only pull request builds
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/master' || github.ref != 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-') || github.run_number }}
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
on: on:
push: push:
branches: [ master, releases/* ] branches: [ master, releases/* ]
@@ -30,7 +24,6 @@ jobs:
analyze: analyze:
name: Analyze name: Analyze
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 60
strategy: strategy:
fail-fast: false fail-fast: false
@@ -42,11 +35,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6.0.1 uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v4 uses: github/codeql-action/init@v1
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -57,7 +50,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v4 uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -71,4 +64,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4 uses: github/codeql-action/analyze@v1

View File

@@ -1,48 +0,0 @@
name: Example builds (default arch)
concurrency:
# Skip intermediate builds: all builds except for builds on the `master`, `main`, or `release-*` branches
# Cancel intermediate builds: only pull request builds
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/master' || github.ref != 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-') || github.run_number }}
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
on:
push:
branches: ['main', 'master', 'releases/*']
pull_request:
workflow_dispatch:
jobs:
test:
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
julia-version: ['1']
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v6.0.1
- uses: actions/setup-node@v6
if: ${{ ! startsWith(github.ref, 'refs/heads/releases') }}
with:
node-version-file: '.tool-versions'
- name: "Install dependencies"
if: ${{ ! startsWith(github.ref, 'refs/heads/releases') }}
run: |
npm install
npm run build
npm run pack
- name: "Set up Julia"
id: setup-julia
uses: ./
with:
version: ${{ matrix.julia-version }}
- run: julia --version
- run: julia --compile=min -O0 -e 'import InteractiveUtils; InteractiveUtils.versioninfo()'
- name: "Check that the correct julia is used and that archive mtimes are maintained"
run: julia --startup-file=no --color=yes ./.github/scripts/common-tests.jl

View File

@@ -1,51 +0,0 @@
name: Example builds (nightly, default arch)
concurrency:
# Skip intermediate builds: all builds except for builds on the `master`, `main`, or `release-*` branches
# Cancel intermediate builds: only pull request builds
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/master' || github.ref != 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-') || github.run_number }}
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
on:
push:
branches: ['main', 'master', 'releases/*']
pull_request:
jobs:
test:
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
julia-version: [nightly, 1.13-nightly]
os:
- ubuntu-latest
- windows-latest
- macos-15-intel # Intel
- macos-14 # Apple Silicon
- macos-latest # Apple Silicon
steps:
- uses: actions/checkout@v6.0.1
- uses: actions/setup-node@v6
if: ${{ ! startsWith(github.ref, 'refs/heads/releases') }}
with:
node-version-file: '.tool-versions'
- name: "Install dependencies"
if: ${{ ! startsWith(github.ref, 'refs/heads/releases') }}
run: |
npm install
npm run build
npm run pack
- name: "Set up Julia (${{ matrix.julia-version }})"
uses: ./
with:
version: ${{ matrix.julia-version }}
- run: julia --version
- run: julia --compile=min -O0 -e 'import InteractiveUtils; InteractiveUtils.versioninfo()'
- name: "Check that the correct julia is used and that archive mtimes are maintained"
run: julia --startup-file=no --color=yes ./.github/scripts/common-tests.jl

View File

@@ -1,11 +1,5 @@
name: Example builds (nightly) name: Example builds (nightly)
concurrency:
# Skip intermediate builds: all builds except for builds on the `master`, `main`, or `release-*` branches
# Cancel intermediate builds: only pull request builds
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/master' || github.ref != 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-') || github.run_number }}
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
on: on:
push: push:
branches: ['main', 'master', 'releases/*'] branches: ['main', 'master', 'releases/*']
@@ -16,11 +10,10 @@ on:
jobs: jobs:
test: test:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
julia-version: [nightly, 1.13-nightly] julia-version: [nightly, 1.8-nightly]
julia-arch: [x64, x86] julia-arch: [x64, x86]
os: [ubuntu-latest, macOS-latest, windows-latest] os: [ubuntu-latest, macOS-latest, windows-latest]
# 32-bit Julia binaries are not available on macOS # 32-bit Julia binaries are not available on macOS
@@ -29,15 +22,9 @@ jobs:
julia-arch: x86 julia-arch: x86
steps: steps:
- uses: actions/checkout@v6.0.1 - uses: actions/checkout@v1.0.0
- uses: actions/setup-node@v6
if: ${{ ! startsWith(github.ref, 'refs/heads/releases') }}
with:
node-version-file: '.tool-versions'
- name: "Install dependencies" - name: "Install dependencies"
if: ${{ ! startsWith(github.ref, 'refs/heads/releases') }}
run: | run: |
npm install npm install
npm run build npm run build
@@ -50,5 +37,3 @@ jobs:
arch: ${{ matrix.julia-arch }} arch: ${{ matrix.julia-arch }}
- run: julia --version - run: julia --version
- run: julia --compile=min -O0 -e 'import InteractiveUtils; InteractiveUtils.versioninfo()' - run: julia --compile=min -O0 -e 'import InteractiveUtils; InteractiveUtils.versioninfo()'
- name: "Check that the correct julia is used and that archive mtimes are maintained"
run: julia --startup-file=no --color=yes ./.github/scripts/common-tests.jl

View File

@@ -1,11 +1,5 @@
name: Example builds name: Example builds
concurrency:
# Skip intermediate builds: all builds except for builds on the `master`, `main`, or `release-*` branches
# Cancel intermediate builds: only pull request builds
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/master' || github.ref != 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-') || github.run_number }}
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
on: on:
push: push:
branches: ['main', 'master', 'releases/*'] branches: ['main', 'master', 'releases/*']
@@ -15,36 +9,21 @@ on:
jobs: jobs:
test: test:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
# include '1.6' here to test info message about lts tag existing julia-version: ['1.0.5', '1', '^1.5.0-beta1']
julia-version: ['1.0.5', '1.2', '^1.5.0-beta1', '1', '1.6', 'lts', 'pre']
julia-arch: [x64, x86] julia-arch: [x64, x86]
os: [ubuntu-latest, macOS-latest, windows-latest] os: [ubuntu-latest, macOS-latest, windows-latest]
# 32-bit Julia binaries are not available on macOS # 32-bit Julia binaries are not available on macOS
exclude: exclude:
- os: macOS-latest - os: macOS-latest
julia-arch: x86 julia-arch: x86
include:
- os: macOS-latest
julia-arch: aarch64
julia-version: 'lts'
- os: macOS-latest
julia-arch: aarch64
julia-version: '1'
steps: steps:
- uses: actions/checkout@v6.0.1 - uses: actions/checkout@v1.0.0
- uses: actions/setup-node@v6
if: ${{ ! startsWith(github.ref, 'refs/heads/releases') }}
with:
node-version-file: '.tool-versions'
- name: "Install dependencies" - name: "Install dependencies"
if: ${{ ! startsWith(github.ref, 'refs/heads/releases') }}
run: | run: |
npm install npm install
npm run build npm run build
@@ -58,5 +37,3 @@ jobs:
arch: ${{ matrix.julia-arch }} arch: ${{ matrix.julia-arch }}
- run: julia --version - run: julia --version
- run: julia --compile=min -O0 -e 'import InteractiveUtils; InteractiveUtils.versioninfo()' - run: julia --compile=min -O0 -e 'import InteractiveUtils; InteractiveUtils.versioninfo()'
- name: "Check that the correct julia is used and that archive mtimes are maintained"
run: julia --startup-file=no --color=yes ./.github/scripts/common-tests.jl

View File

@@ -1,222 +0,0 @@
name: PR Checks
on:
pull_request:
push:
branches:
- master
tags: '*'
concurrency:
# Skip intermediate builds: all builds except for builds on the `master` branch
# Cancel intermediate builds: only pull request builds
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/master' || github.run_number }}
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
permissions:
contents: read
jobs:
finalize-pr-checks:
if: always() # this line is important to keep the `finalize` job from being marked as skipped; do not change or delete this line
runs-on: ubuntu-latest
timeout-minutes: 10
needs:
- checked-in-files
- build
- npm-run-test
- make-targets
- stalecheck-npm-install
steps:
- run: |
echo checked-in-files: ${{ needs.checked-in-files.result }}
echo build: ${{ needs.build.result }}
echo npm-run-test: ${{ needs.npm-run-test.result }}
echo make-targets: ${{ needs.make-targets.result }}
echo stalecheck-npm-install: ${{ needs.stalecheck-npm-install.result }}
- run: exit 1
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
checked-in-files:
timeout-minutes: 30
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
### Check out the repo:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
with:
persist-credentials: false
### Cleanall:
- run: make cleanall
### Install NodeJS
# Unix (non-Windows):
- uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47
if: runner.os != 'Windows'
- run: make unix-asdf-install
if: runner.os != 'Windows'
# Windows:
# Windows does not support asdf, so we have to use `actions/setup-node`
# to install asdf:
- uses: actions/setup-node@dda4788290998366da86b6a4f497909644397bb2
if: runner.os == 'Windows'
with:
node-version-file: '.tool-versions'
### Install the NodeJS packages that we depend on:
- run: make install-packages
### Print some debugging info:
- name: Print the NodeJS version (for debugging)
run: |
which -a node
node --version
which -a npm
npm --version
### Build:
- run: make pack
### Clean (not cleanall!):
- run: make clean
### Make sure there are no uncommited changes
- uses: julia-actions/setup-julia@5c9647d97b78a5debe5164e9eec09d653d29bd71 # v2.6.1
with:
version: '1'
- run: git --no-pager status
- run: git --no-pager diff
- run: julia ./ci/check_uncommitted_changes.jl
build:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
### Check out the repo:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
with:
persist-credentials: false
### Cleanall:
- run: make cleanall
### Install NodeJS
# Unix (non-Windows):
- uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47
if: runner.os != 'Windows'
- run: make unix-asdf-install
if: runner.os != 'Windows'
# Windows:
# Windows does not support asdf, so we have to use `actions/setup-node`
# to install asdf:
- uses: actions/setup-node@dda4788290998366da86b6a4f497909644397bb2
if: runner.os == 'Windows'
with:
node-version-file: '.tool-versions'
### Install the NodeJS packages that we depend on:
- run: make install-packages
### Print some debugging info:
- name: Print the NodeJS version (for debugging)
run: |
which -a node
node --version
which -a npm
npm --version
### Build:
- run: make build
- run: make pack
### Make sure some other `make` targets don't bitrot:
- name: Run some other `make` targets to ensure that they don't bitrot
run: |
make clean
make cleanall
- name: Run all of the "cleaning" `make` targets to ensure that they don't bitrot
run: |
make clean
make cleanall
npm-run-test:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
### Check out the repo:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
with:
persist-credentials: false
### Cleanall:
- run: make cleanall
### Install NodeJS
# Unix (non-Windows):
- uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47
if: runner.os != 'Windows'
- run: make unix-asdf-install
if: runner.os != 'Windows'
# Windows:
# Windows does not support asdf, so we have to use `actions/setup-node`
# to install asdf:
- uses: actions/setup-node@dda4788290998366da86b6a4f497909644397bb2
if: runner.os == 'Windows'
with:
node-version-file: '.tool-versions'
### Install the NodeJS packages that we depend on:
- run: make install-packages
### Print some debugging info:
- name: Print the NodeJS version (for debugging)
run: |
which -a node
node --version
which -a npm
npm --version
### Build:
- run: make build
- run: make test
make-targets: # This is a job to make sure that none of the `make` targets bitrot
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
### Check out the repo:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
with:
persist-credentials: false
### Cleanall:
- run: make cleanall
### Install NodeJS
# Unix (non-Windows):
- uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47
if: runner.os != 'Windows'
- run: make unix-asdf-install
if: runner.os != 'Windows'
### Install the NodeJS packages that we depend on:
- run: make install-packages
### Make sure some other `make` targets don't bitrot:
- name: Run some other `make` targets to ensure that they don't bitrot
run: |
make unix-asdf-install
make install-packages
make build
make pack
make everything-from-scratch
- name: Run all of the "cleaning" `make` targets to ensure that they don't bitrot
run: |
make clean
make cleanall
stalecheck-npm-install:
# In this job, we are basically trying to check if `package-lock.json` is in
# sync with `package-lock.json`.
#
# So, for example, if someone manually edits the `package.json` file, we want
# to make sure that the `package-lock.json` file is not out of sync with the
# `package.json` file.
timeout-minutes: 30
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
### Check out the repo:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
with:
persist-credentials: false
### Install NodeJS
# Unix (non-Windows):
- uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47
if: runner.os != 'Windows'
- run: make unix-asdf-install
if: runner.os != 'Windows'
### Run the master commands for this job:
- run: make clean
- run: npm ci
# - run: npm install --package-lock-only
- run: npm install
### Make sure there are no uncommited changes
- uses: julia-actions/setup-julia@5c9647d97b78a5debe5164e9eec09d653d29bd71 # v2.6.1
with:
version: '1'
- run: git --no-pager status
- run: git --no-pager diff
- run: julia ./ci/check_uncommitted_changes.jl

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
node_modules/ node_modules/
__tests__/runner/* __tests__/runner/*
!dist/

3
.gitmodules vendored
View File

@@ -0,0 +1,3 @@
[submodule "bin"]
path = bin
url = git@github.com:julia-actions/bin.git

View File

@@ -1 +0,0 @@
nodejs 20.11.1

View File

@@ -1,3 +1,11 @@
# Contributing # Dev docs / Contributing guide
Please see the README in the [`devdocs/`](devdocs/) folder. ## Building and tagging a release (requires write access)
1. Test your changes, merge into `master`.
2. Checkout `master`.
3. Bump the version number in [`package.json`](package.json).
4. Run `./bin/build-release julia-actions/setup-julia` to create a release branch and build a release.
5. Push the branch (**without tags**) and verify that CI is passing on it.
6. Run `git push --tags --force` to update the tags.
7. Create a release for the `vX.Y.Z` tag.

View File

@@ -1,49 +0,0 @@
.NOTPARALLEL:
# This is the default target:
.PHONY: pack
pack: build
npm run pack
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PHONY: everything-from-scratch
everything-from-scratch: cleanall install-packages build pack clean
# build does `npm run build`, but does not run `npm run pack`
.PHONY: build
build:
npm run build
.PHONY: test
test:
npm run test
.PHONY: install-packages
install-packages:
rm -rf node_modules/
# Note: we use `npm ci` instead of `npm install`, because we want to make sure
# that we respect the versions in the `package-lock.json` lockfile.
npm ci
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PHONY: clean
clean:
rm -rf node_modules/
.PHONY: cleanall
cleanall: clean
rm -rf lib/
rm -rf dist/
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# asdf does not support Windows.
# On Windows, users need to install the correct version of NodeJS themselves.
.PHONY: unix-asdf-install
unix-asdf-install:
asdf plugin add nodejs # update this list when we add more tools to `.tool-versions`
asdf install
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

166
README.md
View File

@@ -1,36 +1,29 @@
# setup-julia Action # setup-julia Action
[![CodeQL](https://github.com/julia-actions/setup-julia/workflows/CodeQL/badge.svg)](https://securitylab.github.com/tools/codeql) [![CodeQL](https://github.com/julia-actions/setup-julia/workflows/CodeQL/badge.svg)](https://securitylab.github.com/tools/codeql)
[![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=julia-actions/setup-julia)](https://dependabot.com)
This action sets up a Julia environment for use in actions by downloading a specified version of Julia and adding it to PATH. This action sets up a Julia environment for use in actions by downloading a specified version of Julia and adding it to PATH.
## Table of Contents ## Table of Contents
- [setup-julia Action](#setup-julia-action) - [Table of Contents](#table-of-contents)
- [Table of Contents](#table-of-contents) - [Usage](#usage)
- [Usage](#usage) - [Inputs](#inputs)
- [Inputs](#inputs) - [Outputs](#outputs)
- [Outputs](#outputs) - [Basic](#basic)
- [Basic](#basic) - [Julia Versions](#julia-versions)
- [Julia Versions](#julia-versions) - [Matrix Testing](#matrix-testing)
- [Examples](#examples) - [versioninfo](#versioninfo)
- [Prereleases](#prereleases) - [Versioning](#versioning)
- [Recently released versions](#recently-released-versions) - [Debug logs](#debug-logs)
- [Matrix Testing](#matrix-testing) - [Third party information](#third-party-information)
- [64-bit Julia only](#64-bit-julia-only)
- [32-bit Julia](#32-bit-julia)
- [versioninfo](#versioninfo)
- [Versioning](#versioning)
- [Using Dependabot version updates to keep your GitHub Actions up to date](#using-dependabot-version-updates-to-keep-your-github-actions-up-to-date)
- [Debug logs](#debug-logs)
- [Third party information](#third-party-information)
- [Contributing to this repo](#contributing-to-this-repo)
## Usage ## Usage
### Inputs ### Inputs
```yaml ```yaml
- uses: julia-actions/setup-julia@v2 - uses: julia-actions/setup-julia@v1
with: with:
# The Julia version that will be installed and added as `julia` to the PATH. # The Julia version that will be installed and added as `julia` to the PATH.
# See "Julia Versions" below for a list of valid values. # See "Julia Versions" below for a list of valid values.
@@ -38,10 +31,10 @@ This action sets up a Julia environment for use in actions by downloading a spec
# Warning: It is strongly recommended to wrap this value in quotes. # Warning: It is strongly recommended to wrap this value in quotes.
# Otherwise, the YAML parser used by GitHub Actions parses certain # Otherwise, the YAML parser used by GitHub Actions parses certain
# versions as numbers which causes the wrong version to be selected. # versions as numbers which causes the wrong version to be selected.
# For example, `1.10` may be parsed as `1.1`. # For example, `1.0` may be parsed as `1`.
# #
# Default: '1' # Default: '1'
version: '1' version: ''
# The architecture of the Julia binaries. # The architecture of the Julia binaries.
# #
@@ -51,10 +44,8 @@ This action sets up a Julia environment for use in actions by downloading a spec
# #
# Supported values: x64 | x86 | aarch64 (untested) # Supported values: x64 | x86 | aarch64 (untested)
# #
# Note: you can use X64, X86, and ARM64 as synonyms for x64, x86, and aarch64, respectively. # Default: x64
# arch: ''
# Specifying 'default' uses the architecture of the runner executing the job.
arch: 'default'
# Set the display setting for printing InteractiveUtils.versioninfo() after installing. # Set the display setting for printing InteractiveUtils.versioninfo() after installing.
# #
@@ -70,12 +61,7 @@ This action sets up a Julia environment for use in actions by downloading a spec
# never: Never print versioninfo # never: Never print versioninfo
# #
# Default: false # Default: false
show-versioninfo: 'false' show-versioninfo: ''
# Set the path to the project directory or file to use when resolving some versions (e.g. `min`).
#
# Defaults to using JULIA_PROJECT if defined, otherwise '.'
project: ${{ env.JULIA_PROJECT }} or '.' (if JULIA_PROJECT is unset)
``` ```
### Outputs ### Outputs
@@ -99,10 +85,10 @@ outputs:
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1.0.0
- uses: julia-actions/setup-julia@v2 - uses: julia-actions/setup-julia@v1
with: with:
version: '1.10' version: 1.0.4
- run: julia -e 'println("Hello, World!")' - run: julia -e 'println("Hello, World!")'
``` ```
@@ -110,55 +96,22 @@ steps:
You can either specify specific Julia versions or version ranges. If you specify a version range, the **highest** available Julia version that matches the range will be selected. You can either specify specific Julia versions or version ranges. If you specify a version range, the **highest** available Julia version that matches the range will be selected.
> **Warning** **Warning:** It is strongly recommended to wrap versions in quotes. Otherwise, the YAML parser used by GitHub Actions parses certain versions as numbers which causes the wrong version to be selected. For example, `1.0` may be parsed as `1`.
>
> It is strongly recommended to wrap versions in quotes. Otherwise, the YAML parser used by GitHub Actions parses certain versions as numbers which causes the wrong version to be selected. For example, `1.0` may be parsed as `1`.
#### Examples #### Examples
- `'1.2.0'` is a valid semver version. The action will try to download exactly this version. If it's not available, the build step will fail. - `1.2.0` is a valid semver version. The action will try to download exactly this version. If it's not available, the build step will fail.
- `'1.0'` is a version range that will match the highest available Julia version that starts with `1.0`, e.g. `1.0.5`, excluding pre-releases. - `1.0` is a version range that will match the highest available Julia version that starts with `1.0`, e.g. `1.0.5`, excluding pre-releases.
- `'^1.3.0-rc1'` is a **caret** version range that includes pre-releases of `1.3.0` starting at `rc1`. It matches all versions `≥ 1.3.0-rc1` and `< 2.0.0`. - `^1.3.0-rc1` is a **caret** version range that includes pre-releases starting at `rc1`. It matches all versions `≥ 1.3.0-rc1` and `< 2.0.0`.
- `'~1.3.0-rc1'` is a **tilde** version range that includes pre-releases of `1.3.0` starting at `rc1`. It matches all versions `≥ 1.3.0-rc1` and `< 1.4.0`. - `~1.3.0-rc1` is a **tilde** version range that includes pre-releases starting at `rc1`. It matches all versions `≥ 1.3.0-rc1` and `< 1.4.0`.
- `'^1.3.0-0'` is a **caret** version range that includes _all_ pre-releases of `1.3.0`. It matches all versions `≥ 1.3.0-` and `< 2.0.0`. - `^1.3.0-0` is a **caret** version range that includes _all_ pre-releases. It matches all versions `≥ 1.3.0-` and `< 2.0.0`.
- `'~1.3.0-0'` is a **tilde** version range that includes _all_ pre-releases of `1.3.0`. It matches all versions `≥ 1.3.0-` and `< 1.4.0`. - `~1.3.0-0` is a **tilde** version range that includes _all_ pre-releases. It matches all versions `≥ 1.3.0-` and `< 1.4.0`.
- `'1'` will install the latest v1 version of Julia. - `nightly` will install the latest nightly build.
- `'lts'` will install the latest LTS build. - `1.7-nightly` will install the latest nightly build for the upcoming 1.7 release. This version will only be available during certain phases of the Julia release cycle.
- `'pre'` will install the latest prerelease build (RCs, betas, and alphas).
- `'nightly'` will install the latest nightly build.
- `'1.7-nightly'` will install the latest nightly build for the upcoming 1.7 release. This version will only be available during certain phases of the Julia release cycle.
- `'min'` will install the earliest supported version of Julia compatible with the project. Especially useful in monorepos.
Internally the action uses node's semver package to resolve version ranges. Its [documentation](https://github.com/npm/node-semver#advanced-range-syntax) contains more details on the version range syntax. You can test what version will be selected for a given input in this JavaScript [REPL](https://repl.it/@SaschaMann/setup-julia-version-logic). Internally the action uses node's semver package to resolve version ranges. Its [documentation](https://github.com/npm/node-semver#advanced-range-syntax) contains more details on the version range syntax. You can test what version will be selected for a given input in this JavaScript [REPL](https://repl.it/@SaschaMann/setup-julia-version-logic).
#### Prereleases The available Julia versions are pulled from [`versions.json`](https://julialang-s3.julialang.org/bin/versions.json). This file is automatically updated once a day. Therefore it may take up to 24 hours until a newly released Julia version is available in the action.
There are two methods of including pre-releases in version matching:
1. Including the pre-release tag in the version itself, e.g. `'^1.3.0-rc1'`.
2. Setting the input `include-all-prereleases` to `true`.
These behave slightly differently.
1. If the version `a.b.c` contains pre-release tag, all pre-releases of version `a.b.c` will be included in the version matching.
For example, `^1.3.0-rc1` would match `1.3.0-rc2` but would **not** match `1.4.0-rc1` once released.
2. If `include-preleases` is set to true, **all** pre-releases of all versions will be included in the version matching. In this case, `^1.3.0-rc1` would match `1.4.0-rc1` once released.
**Example:** Without `include-all-prereleases: true`, the version `^1.3.0-rc1` would match `1.3.0-rc1`, `1.3.0-rc2`, `1.3.0`, `1.4.0` once they are released.
With `include-all-prereleases: true`, it would match `1.3.0-rc1`, `1.3.0-rc2`, `1.3.0`, `1.4.0-rc1`, `1.4.0`.
If you want to run tests against the latest tagged version, no matter what version that is, you can use
```yaml
- uses: julia-actions/setup-julia@v2
with:
version: '1'
include-all-prereleases: true
```
#### Recently released versions
The available Julia versions are pulled from [`versions.json`](https://julialang-s3.julialang.org/bin/versions.json).
### Matrix Testing ### Matrix Testing
@@ -176,9 +129,9 @@ jobs:
os: [ubuntu-latest, windows-latest, macOS-latest] os: [ubuntu-latest, windows-latest, macOS-latest]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1.0.0
- name: "Set up Julia" - name: "Set up Julia"
uses: julia-actions/setup-julia@v2 uses: julia-actions/setup-julia@v1
with: with:
version: ${{ matrix.julia-version }} version: ${{ matrix.julia-version }}
- run: julia -e 'println("Hello, World!")' - run: julia -e 'println("Hello, World!")'
@@ -194,23 +147,17 @@ jobs:
strategy: strategy:
matrix: matrix:
julia-version: ['1.0', '1.2.0', '^1.3.0-rc1'] julia-version: ['1.0', '1.2.0', '^1.3.0-rc1']
julia-arch: [x64, x86, aarch64] julia-arch: [x64, x86]
os: [ubuntu-latest, windows-latest, macOS-latest] os: [ubuntu-latest, windows-latest, macOS-latest]
# exclude unavailable/unwanted architectures # 32-bit Julia binaries are not available on macOS
exclude: exclude:
- os: macOS-latest - os: macOS-latest
julia-arch: x86 julia-arch: x86
- os: macOS-latest
julia-arch: x64 # can be run but via rosetta on apple silicon runners
- os: ubuntu-latest
julia-arch: aarch64
- os: windows-latest
julia-arch: aarch64
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1.0.0
- name: "Set up Julia" - name: "Set up Julia"
uses: julia-actions/setup-julia@v2 uses: julia-actions/setup-julia@v1
with: with:
version: ${{ matrix.julia-version }} version: ${{ matrix.julia-version }}
arch: ${{ matrix.julia-arch }} arch: ${{ matrix.julia-arch }}
@@ -235,9 +182,9 @@ jobs:
julia-arch: x86 julia-arch: x86
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1.0.0
- name: "Set up Julia" - name: "Set up Julia"
uses: julia-actions/setup-julia@v2 uses: julia-actions/setup-julia@v1
with: with:
version: ${{ matrix.julia-version }} version: ${{ matrix.julia-version }}
- run: julia -e 'println("Hello, World!")' - run: julia -e 'println("Hello, World!")'
@@ -260,44 +207,23 @@ This action follows [GitHub's advice](https://help.github.com/en/articles/about-
If you don't want to deal with updating the version of the action, similiarly to how Travis CI handles it, use `latest` or major version branches. [Dependabot](https://dependabot.com/) can also be used to automatically create Pull Requests to update actions used in your workflows. If you don't want to deal with updating the version of the action, similiarly to how Travis CI handles it, use `latest` or major version branches. [Dependabot](https://dependabot.com/) can also be used to automatically create Pull Requests to update actions used in your workflows.
It's unlikely, but not impossible, that there will be breaking changes post-v2.0.0 unless a new major version of Julia is introduced. It's unlikely, but not impossible, that there will be breaking changes post-v1.0.0 unless a new major version of Julia is introduced.
You can specify commits, branches or tags in your workflows as follows: You can specify commits, branches or tags in your workflows as follows:
```yaml ```yaml
steps: steps:
- uses: julia-actions/setup-julia@f2258781c657ad9b4b88072c5eeaf9ec8c370874 # commit SHA of the tagged 2.0.0 commit - uses: julia-actions/setup-julia@d3ce119a16594ea9e5d7974813970c73b6ab9e94 # commit SHA of the tagged 1.4.1 commit
- uses: julia-actions/setup-julia@latest # latest version tag (may break existing workflows) - uses: julia-actions/setup-julia@latest # latest version tag (may break existing workflows)
- uses: julia-actions/setup-julia@v2 # major version tag - uses: julia-actions/setup-julia@v1 # major version tag
- uses: julia-actions/setup-julia@v2.0 # minor version tag - uses: julia-actions/setup-julia@v1.4 # minor version tag
- uses: julia-actions/setup-julia@v2.0.0 # specific version tag - uses: julia-actions/setup-julia@v1.4.1 # specific version tag
``` ```
If your workflow requires access to secrets, you should always pin it to a commit SHA instead of a tag. If your workflow requires access to secrets, you should always pin it to a commit SHA instead of a tag.
This will protect you in case a bad actor gains access to the setup-julia repo. This will protect you in case a bad actor gains access to the setup-julia repo.
You can find more information in [GitHub's security hardening guide](https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions). You can find more information in [GitHub's security hardening guide](https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions).
## Using Dependabot version updates to keep your GitHub Actions up to date
We highly recommend that you set up Dependabot version updates on your repo to keep your GitHub Actions up to date.
To set up Dependabot version updates, create a file named `.github/dependabot.yml` in your repo with the following contents:
```yaml
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 99
labels:
- "dependencies"
- "github-actions"
```
For more details on Dependabot version updates, see the [GitHub Dependabot documentation](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates).
## Debug logs ## Debug logs
You can enable [Step Debug Logs](https://github.com/actions/toolkit/blob/main/docs/action-debugging.md#step-debug-logs) for more detailed logs. You can enable [Step Debug Logs](https://github.com/actions/toolkit/blob/main/docs/action-debugging.md#step-debug-logs) for more detailed logs.
@@ -306,7 +232,3 @@ Note that when debug logs are enabled, a request will be sent to `https://httpbi
## Third party information ## Third party information
Parts of this software have been derived from other open source software. Parts of this software have been derived from other open source software.
See [THIRD_PARTY_NOTICE.md](THIRD_PARTY_NOTICE.md) for details. See [THIRD_PARTY_NOTICE.md](THIRD_PARTY_NOTICE.md) for details.
## Contributing to this repo
Please see the README in the [`devdocs/`](devdocs/) folder.

File diff suppressed because it is too large Load Diff

View File

@@ -4,30 +4,30 @@
import * as path from 'path' import * as path from 'path'
import * as io from '@actions/io' import * as io from '@actions/io'
import * as semver from 'semver'
import nock = require('nock') import nock = require('nock')
import * as semver from 'semver'
const testVersions = [ const testVersions = [
'0.1.2', '0.1.2', '0.2.0', '0.2.1', '0.3.0',
'0.2.0', '0.2.1', '0.3.1', '0.3.10', '0.3.11', '0.3.12',
'0.3.0', '0.3.1', '0.3.10', '0.3.11', '0.3.12', '0.3.2', '0.3.3', '0.3.4', '0.3.5', '0.3.6', '0.3.7', '0.3.8', '0.3.9', '0.3.2', '0.3.3', '0.3.4', '0.3.5',
'0.4.0', '0.4.0-rc1', '0.4.0-rc2', '0.4.0-rc3', '0.4.0-rc4', '0.4.1', '0.4.2', '0.4.3', '0.4.4', '0.4.5', '0.4.6', '0.4.7', '0.3.6', '0.3.7', '0.3.8', '0.3.9',
'0.5.0', '0.5.0-rc0', '0.5.0-rc1', '0.5.0-rc2', '0.5.0-rc3', '0.5.0-rc4', '0.5.1', '0.5.2', '0.4.0', '0.4.0-rc1', '0.4.0-rc2', '0.4.0-rc3',
'0.6.0', '0.6.0-pre.alpha', '0.6.0-pre.beta', '0.6.0-rc1', '0.6.0-rc2', '0.6.0-rc3', '0.6.1', '0.6.2', '0.6.3', '0.6.4', '0.4.0-rc4', '0.4.1', '0.4.2', '0.4.3',
'0.7.0', '0.7.0-alpha', '0.7.0-beta', '0.7.0-beta2', '0.7.0-rc1', '0.7.0-rc2', '0.7.0-rc3', '0.4.4', '0.4.5', '0.4.6', '0.4.7',
'1.0.0', '1.0.0-rc1', '1.0.1', '1.0.2', '1.0.3', '1.0.4', '1.0.5', '0.5.0', '0.5.0-rc0', '0.5.0-rc1', '0.5.0-rc2',
'1.1.0', '1.1.0-rc1', '1.1.0-rc2', '1.1.1', '0.5.0-rc3', '0.5.0-rc4', '0.5.1', '0.5.2',
'1.2.0', '1.2.0-rc1', '1.2.0-rc2', '1.2.0-rc3', '0.6.0', '0.6.0-pre.alpha', '0.6.0-pre.beta', '0.6.0-rc1',
'1.3.0', '1.3.0-alpha', '1.3.0-rc1', '1.3.0-rc2', '1.3.0-rc3', '1.3.0-rc4', '1.3.0-rc5', '1.3.1', '0.6.0-rc2', '0.6.0-rc3', '0.6.1', '0.6.2',
'1.4.0', '1.4.0-rc1', '1.4.0-rc2', '1.4.1', '1.4.2', '0.6.3', '0.6.4', '0.7.0', '0.7.0-alpha',
'1.5.0', '1.5.0-beta1', '1.5.0-rc1', '1.5.0-rc2', '1.5.1', '1.5.2', '1.5.3', '1.5.4', '0.7.0-beta', '0.7.0-beta2', '0.7.0-rc1', '0.7.0-rc2',
'1.6.0', '1.6.0-beta1', '1.6.0-rc1', '1.6.0-rc2', '1.6.0-rc3', '1.6.1', '1.6.2', '1.6.3', '1.6.4', '1.6.5', '1.6.6', '1.6.7', '0.7.0-rc3', '1.0.0', '1.0.0-rc1', '1.0.1',
'1.7.0', '1.7.0-beta1', '1.7.0-beta2', '1.7.0-beta3', '1.7.0-beta4', '1.7.0-rc1', '1.7.0-rc2', '1.7.0-rc3', '1.7.1', '1.7.2', '1.7.3', '1.0.2', '1.0.3', '1.0.4', '1.0.5',
'1.8.0', '1.8.0-beta1', '1.8.0-beta2', '1.8.0-beta3', '1.8.0-rc1', '1.8.0-rc2', '1.8.0-rc3', '1.8.0-rc4', '1.8.1', '1.8.2', '1.8.3', '1.8.4', '1.8.5', '1.1.0', '1.1.0-rc1', '1.1.0-rc2', '1.1.1',
'1.9.0', '1.9.0-alpha1', '1.9.0-beta1', '1.9.0-beta2', '1.9.0-beta3', '1.9.0-beta4', '1.9.0-rc1', '1.9.0-rc2', '1.9.0-rc3', '1.9.1', '1.9.2', '1.9.3', '1.9.4', '1.2.0', '1.2.0-rc1', '1.2.0-rc2', '1.2.0-rc3',
'1.10.0', '1.10.0-alpha1', '1.10.0-beta1', '1.10.0-beta2', '1.10.0-beta3', '1.10.0-rc1', '1.10.0-rc2', '1.10.0-rc3', '1.10.1', '1.10.2', '1.10.3', '1.10.4', '1.10.5', '1.3.0-alpha', '1.3.0-rc1', '1.3.0-rc2', '1.3.0-rc3',
'1.11.0', '1.11.0-alpha1', '1.11.0-alpha2', '1.11.0-beta1', '1.11.0-beta2', '1.11.0-rc1', '1.11.0-rc2', '1.11.0-rc3', '1.11.0-rc4', '1.3.0-rc4'
] ]
const toolDir = path.join(__dirname, 'runner', 'tools') const toolDir = path.join(__dirname, 'runner', 'tools')
@@ -38,135 +38,6 @@ process.env['RUNNER_TOOL_CACHE'] = toolDir
process.env['RUNNER_TEMP'] = tempDir process.env['RUNNER_TEMP'] = tempDir
import * as installer from '../src/installer' import * as installer from '../src/installer'
import exp from 'constants'
describe("getProjectFilePath tests", () => {
let orgJuliaProject
let orgWorkingDir
beforeEach(() => {
orgJuliaProject = process.env["JULIA_PROJECT"]
orgWorkingDir = process.cwd()
delete process.env["JULIA_PROJECT"]
})
afterEach(() => {
process.env["JULIA_PROJECT"] = orgJuliaProject
process.chdir(orgWorkingDir)
})
it("Can determine project file is missing", () => {
expect(() => installer.getProjectFilePath("DNE.toml")).toThrow("Unable to locate project file")
expect(() => installer.getProjectFilePath(fixtureDir)).toThrow("Unable to locate project file")
expect(() => installer.getProjectFilePath()).toThrow("Unable to locate project file")
})
it('Can determine project file from a directory', () => {
expect(installer.getProjectFilePath(path.join(fixtureDir, "PkgA"))).toEqual(path.join(fixtureDir, "PkgA", "Project.toml"))
expect(installer.getProjectFilePath(path.join(fixtureDir, "PkgB"))).toEqual(path.join(fixtureDir, "PkgB", "JuliaProject.toml"))
})
it("Prefers using JuliaProject.toml over Project.toml", () => {
expect(installer.getProjectFilePath(path.join(fixtureDir, "PkgC"))).toEqual(path.join(fixtureDir, "PkgC", "JuliaProject.toml"))
})
it("Can determine project from JULIA_PROJECT", () => {
process.env["JULIA_PROJECT"] = path.join(fixtureDir, "PkgA")
expect(installer.getProjectFilePath()).toEqual(path.join(fixtureDir, "PkgA", "Project.toml"))
})
it("Can determine project from the current working directory", () => {
process.chdir(path.join(fixtureDir, "PkgA"));
expect(installer.getProjectFilePath()).toEqual("Project.toml")
})
it("Ignores JULIA_PROJECT when argument is used", () => {
process.env["JULIA_PROJECT"] = path.join(fixtureDir, "PkgB")
expect(installer.getProjectFilePath(path.join(fixtureDir, "PkgA"))).toEqual(path.join(fixtureDir, "PkgA", "Project.toml"))
})
})
describe("validJuliaCompatRange tests", () => {
it('Handles default caret specifier', () => {
expect(installer.validJuliaCompatRange("1")).toEqual(semver.validRange("^1"))
expect(installer.validJuliaCompatRange("1.2")).toEqual(semver.validRange("^1.2"))
expect(installer.validJuliaCompatRange("1.2.3")).toEqual(semver.validRange("^1.2.3"))
// TODO: Pkg.jl currently does not support pre-release entries in compat so ideally this would fail
expect(installer.validJuliaCompatRange("1.2.3-rc1")).toEqual(semver.validRange("^1.2.3-rc1"))
})
it('Handle surrounding whitespace', () => {
expect(installer.validJuliaCompatRange(" 1")).toEqual(semver.validRange("^1"))
expect(installer.validJuliaCompatRange("1 ")).toEqual(semver.validRange("^1"))
expect(installer.validJuliaCompatRange(" 1 ")).toEqual(semver.validRange("^1"))
})
it('Handles version ranges with specifiers', () => {
expect(installer.validJuliaCompatRange("^1.2.3")).toEqual(semver.validRange("^1.2.3"))
expect(installer.validJuliaCompatRange("~1.2.3")).toEqual(semver.validRange("~1.2.3"))
expect(installer.validJuliaCompatRange("=1.2.3")).toEqual(semver.validRange("=1.2.3"))
expect(installer.validJuliaCompatRange(">=1.2.3")).toEqual(">=1.2.3")
expect(installer.validJuliaCompatRange("≥1.2.3")).toEqual(">=1.2.3")
expect(installer.validJuliaCompatRange("<1.2.3")).toEqual("<1.2.3")
})
it('Handles whitespace after specifiers', () => {
expect(installer.validJuliaCompatRange("^ 1.2.3")).toBeNull()
expect(installer.validJuliaCompatRange("~ 1.2.3")).toBeNull()
expect(installer.validJuliaCompatRange("= 1.2.3")).toBeNull()
expect(installer.validJuliaCompatRange(">= 1.2.3")).toEqual(">=1.2.3")
expect(installer.validJuliaCompatRange("≥ 1.2.3")).toEqual(">=1.2.3")
expect(installer.validJuliaCompatRange("< 1.2.3")).toEqual("<1.2.3")
})
it('Handles hypen ranges', () => {
expect(installer.validJuliaCompatRange("1.2.3 - 4.5.6")).toEqual(semver.validRange("1.2.3 - 4.5.6"))
expect(installer.validJuliaCompatRange("1.2.3-rc1 - 4.5.6")).toEqual(semver.validRange("1.2.3-rc1 - 4.5.6"))
expect(installer.validJuliaCompatRange("1.2.3-rc1-4.5.6")).toEqual(semver.validRange("^1.2.3-rc1-4.5.6")) // A version number and not a hypen range
expect(installer.validJuliaCompatRange("1.2.3-rc1 -4.5.6")).toBeNull()
expect(installer.validJuliaCompatRange("1.2.3-rc1- 4.5.6")).toBeNull() // Whitespace separate version ranges
})
it("Returns null AND operator on version ranges", () => {
expect(installer.validJuliaCompatRange("")).toBeNull()
expect(installer.validJuliaCompatRange("1 2 3")).toBeNull()
expect(installer.validJuliaCompatRange("1- 2")).toBeNull()
expect(installer.validJuliaCompatRange("<1 <1")).toBeNull()
expect(installer.validJuliaCompatRange("< 1 < 1")).toBeNull()
expect(installer.validJuliaCompatRange("< 1 < 1")).toBeNull()
})
it('Returns null with invalid specifiers', () => {
expect(installer.validJuliaCompatRange("<=1.2.3")).toBeNull()
expect(installer.validJuliaCompatRange("≤1.2.3")).toBeNull()
expect(installer.validJuliaCompatRange("*")).toBeNull()
})
it("Handles OR operator on version ranges", () => {
expect(installer.validJuliaCompatRange("1, 2, 3")).toEqual(semver.validRange("^1 || ^2 || ^3"))
expect(installer.validJuliaCompatRange("1, 2 - 3, ≥ 4")).toEqual(semver.validRange("^1 || >=2 <=3 || >=4"))
expect(installer.validJuliaCompatRange(",")).toBeNull()
})
})
describe("readJuliaCompatRange tests", () => {
it('Can determine Julia compat entries', () => {
const toml = '[compat]\njulia = "1, ^1.1, ~1.2, >=1.3, 1.4 - 1.5"'
expect(installer.readJuliaCompatRange(toml)).toEqual(semver.validRange("^1 || ^1.1 || ~1.2 || >=1.3 || 1.4 - 1.5"))
})
it('Throws with invalid version ranges', () => {
expect(() => installer.readJuliaCompatRange('[compat]\njulia = ""')).toThrow("Invalid version range")
expect(() => installer.readJuliaCompatRange('[compat]\njulia = "1 2 3"')).toThrow("Invalid version range")
})
it('Handle missing compat entries', () => {
expect(installer.readJuliaCompatRange("")).toEqual("*")
expect(installer.readJuliaCompatRange("[compat]")).toEqual("*")
})
})
describe('version matching tests', () => { describe('version matching tests', () => {
describe('specific versions', () => { describe('specific versions', () => {
@@ -184,33 +55,14 @@ describe('version matching tests', () => {
expect(installer.getJuliaVersion([], 'nightly')).toEqual('nightly') expect(installer.getJuliaVersion([], 'nightly')).toEqual('nightly')
expect(installer.getJuliaVersion(testVersions, 'nightly')).toEqual('nightly') expect(installer.getJuliaVersion(testVersions, 'nightly')).toEqual('nightly')
}) })
it('LTS', () => {
// Update test when LTS is updated
expect(installer.getJuliaVersion(testVersions, 'lts')).toEqual(installer.getJuliaVersion(testVersions, '1.10'))
expect(installer.getJuliaVersion(testVersions, 'lts')).toEqual('1.10.5')
})
it('pre', () => {
expect(installer.getJuliaVersion(testVersions, 'pre')).toEqual('1.11.0')
})
}) })
describe('version ranges', () => { describe('version ranges', () => {
it('Chooses the highest available version that matches the input', () => { it('Chooses the highest available version that matches the input', () => {
expect(installer.getJuliaVersion(testVersions, '1')).toEqual('1.11.0') expect(installer.getJuliaVersion(testVersions, '1')).toEqual('1.2.0')
expect(installer.getJuliaVersion(testVersions, '1.0')).toEqual('1.0.5') expect(installer.getJuliaVersion(testVersions, '1.0')).toEqual('1.0.5')
expect(installer.getJuliaVersion(testVersions, '^1.3.0-rc1')).toEqual('1.11.0') expect(installer.getJuliaVersion(testVersions, '^1.3.0-rc1')).toEqual('1.3.0-rc4')
expect(installer.getJuliaVersion(testVersions, '^1.2.0-rc1')).toEqual('1.11.0') expect(installer.getJuliaVersion(testVersions, '^1.2.0-rc1')).toEqual('1.2.0')
expect(installer.getJuliaVersion(testVersions, '^1.10.0-rc1')).toEqual('1.11.0')
})
})
describe('include-prereleases', () => {
it('Chooses the highest available version that matches the input including prereleases', () => {
expect(installer.getJuliaVersion(testVersions, '^1.2.0-0', true)).toEqual('1.11.0')
expect(installer.getJuliaVersion(testVersions, '1', true)).toEqual('1.11.0')
expect(installer.getJuliaVersion(testVersions, '^1.2.0-0', false)).toEqual('1.11.0')
}) })
}) })
@@ -224,34 +76,6 @@ describe('version matching tests', () => {
}) })
}) })
}) })
describe('julia compat versions', () => {
it('Understands "min"', () => {
let versions = ["1.6.7", "1.7.1-rc1", "1.7.1-rc2", "1.7.1", "1.7.2", "1.8.0"]
expect(installer.getJuliaVersion(versions, "min", false, "^1.7")).toEqual("1.7.1")
expect(installer.getJuliaVersion(versions, "min", true, "^1.7")).toEqual("1.7.1-rc1")
versions = ["1.6.7", "1.7.3-rc1", "1.7.3-rc2", "1.8.0"]
expect(installer.getJuliaVersion(versions, "min", false, "^1.7")).toEqual("1.8.0")
expect(installer.getJuliaVersion(versions, "min", true, "^1.7")).toEqual("1.7.3-rc1")
expect(installer.getJuliaVersion(versions, "min", false, "~1.7 || ~1.8 || ~1.9")).toEqual("1.8.0")
expect(installer.getJuliaVersion(versions, "min", true, "~1.7 || ~1.8 || ~1.9")).toEqual("1.7.3-rc1")
expect(installer.getJuliaVersion(versions, "min", false, "~1.8 || ~1.7 || ~1.9")).toEqual("1.8.0")
expect(installer.getJuliaVersion(versions, "min", true, "~1.8 || ~1.7 || ~1.9")).toEqual("1.7.3-rc1")
expect(installer.getJuliaVersion(versions, "min", false, "1.7 - 1.9")).toEqual("1.8.0")
expect(installer.getJuliaVersion(versions, "min", true, "1.7 - 1.9")).toEqual("1.7.3-rc1")
expect(installer.getJuliaVersion(versions, "min", true, "< 1.9.0")).toEqual("1.6.7")
expect(installer.getJuliaVersion(versions, "min", true, ">= 1.6.0")).toEqual("1.6.7")
// NPM's semver package treats "1.7" as "~1.7" instead of "^1.7" like Julia
expect(() => installer.getJuliaVersion(versions, "min", false, "1.7")).toThrow("Could not find a Julia version that matches")
expect(() => installer.getJuliaVersion(versions, "min", true, "")).toThrow("Julia project file does not specify a compat for Julia")
})
})
}) })
describe('installer tests', () => { describe('installer tests', () => {

View File

@@ -3,31 +3,23 @@ description: 'Setup a Julia environment and add it to the PATH'
author: 'Sascha Mann' author: 'Sascha Mann'
inputs: inputs:
version: version:
description: 'The Julia version to download (if necessary) and use. Use a string input to avoid unwanted decimal conversion e.g. 1.10 without quotes will be interpreted as 1.1. Examples: "1", "1.10", "lts", "pre"' description: 'The Julia version to download (if necessary) and use. Example: 1.0.4'
default: '1' default: '1'
include-all-prereleases:
description: 'Include prereleases when matching the Julia version to available versions.'
required: false
default: 'false'
arch: arch:
description: 'Architecture of the Julia binaries. Defaults to the architecture of the runner executing the job.' description: 'Architecture of the Julia binaries. Defaults to x64.'
required: false required: false
default: 'default' default: 'x64'
show-versioninfo: show-versioninfo:
description: 'Display InteractiveUtils.versioninfo() after installing' description: 'Display InteractiveUtils.versioninfo() after installing'
required: false required: false
default: 'false' default: 'false'
project:
description: 'The path to the project directory or file to use when resolving some versions (e.g. min)'
required: false
default: '' # Special value which fallsback to using JULIA_PROJECT if defined, otherwise "."
outputs: outputs:
julia-version: julia-version:
description: 'The installed Julia version. May vary from the version input if a version range was given as input.' description: 'The installed Julia version. May vary from the version input if a version range was given as input.'
julia-bindir: julia-bindir:
description: 'Path to the directory containing the Julia executable. Equivalent to JULIA_BINDIR: https://docs.julialang.org/en/v1/manual/environment-variables/#JULIA_BINDIR' description: 'Path to the directory containing the Julia executable. Equivalent to JULIA_BINDIR: https://docs.julialang.org/en/v1/manual/environment-variables/#JULIA_BINDIR'
runs: runs:
using: 'node20' using: 'node12'
main: 'dist/index.js' main: 'dist/index.js'
branding: branding:
icon: 'download' icon: 'download'

1
bin Submodule

Submodule bin added at 31b4b500a3

View File

@@ -1,16 +0,0 @@
const cmd = `git --no-pager diff --exit-code --stat`
const proc = run(pipeline(cmd; stdin, stdout, stderr); wait = false)
wait(proc)
@info "" success(proc) proc.exitcode
if !success(proc)
recommended_cmd = "make everything-from-scratch"
msg = "##[error] found changed files after build. " *
"Please run `$(recommended_cmd)` and " *
"then check in all changes."
println(stderr, msg)
exit(1)
end

View File

@@ -1,7 +0,0 @@
# Devdocs for the `setup-julia` repo
If you want to make a pull request to this repo, please read the following:
1. [Local development](local_setup.md)
If you have commit access to this repo, you may be interested in the following:
1. [Making a new release of this action](making_a_new_release.md)

View File

@@ -1,49 +0,0 @@
# Local development
## 1. Clone the repo
```bash
git clone git@github.com:julia-actions/setup-julia.git
cd setup-julia
```
## 2. Install NodeJS
### Unix, using `asdf` (recommended, but not required)
First, make sure that you have installed [`asdf`](https://asdf-vm.com/) on your local machine.
Then, `cd` to your clone of the repo and run the following commands:
```bash
asdf plugin add nodejs
asdf install
```
This will use `asdf` to install the correct version of NodeJS.
### Unix, but not using `asdf`
Instead of using `asdf`, you can instead choose to install NodeJS manually.
First, check the `.tool-versions` file in this repo, and see what version of NodeJS you need. Then, install that same version of NodejS on your local machine.
### Windows
`asdf` does not (currently) support Windows. So on Windows, you have to install NodeJS manually.
First, check the `.tool-versions` file in this repo, and see what version of NodeJS you need. Then, install that same version of NodejS on your local machine.
## 3. Working locally
First, `cd` to your clone of the repo. Now you can run the following commands:
```bash
npm ci
npm run build
npm run pack
```
When you are ready, you can commit your changes and push them to your PR.

View File

@@ -1,56 +0,0 @@
# Making a new release of this action (requires commit access)
In this guide, as an example, `v2.2.0` refers to the version number of the new release that you want to make.
## Part 1: Use the Git CLI to create and push the Git tags
Step 1: Create a new lightweight tag of the form `vMAJOR.MINOR.PATCH`.
```bash
git clone git@github.com:julia-actions/setup-julia.git
cd setup-julia
git fetch --all --tags
git checkout main
git --no-pager log -1
# Take note of the commit hash here.
# Now, create a new lightweight tag of the form `vMAJOR.MINOR.PATCH`.
#
# Replace `commit_hash` with the commit hash that you obtained from the
# `git log -1` step.
#
# Replace `v2.2.0` with the actual version number that you want to use.
git tag v2.2.0 commit_hash
```
Step 2: Once you've created the new release, you need to update the `v2` tag to point to the new release. For example, suppose that the previous release was `v2.1.0`, and suppose that you just created the new release `v2.2.0`. You need to update the `v2` tag so that it points to `v2.2.0`. Here are the commands:
```bash
# Create/update the new v2 tag locally, where the new v2 tag will point to the
# release that you created in the previous step.
#
# Make sure to change `v2.2.0` to the actual value for the tag that you just
# created in the previous step.
#
# The `-f` flag forcibly overwrites the old
# `v2` tag (if it exists).
git tag -f v2 v2.2.0
```
Step 3: Now you need to push the tags:
```bash
# Regular-push the new `v2.2.0` tag:
git push origin tag v2.2.0
# Force-push the new v2 tag:
git push origin tag v2 --force
```
## Part 2: Create the GitHub Release
Go to the [Releases](https://github.com/julia-actions/setup-julia/releases) section of this repo and create a new release (using the GitHub web interface).
For the "choose a tag" drop-down field, select the `v2.2.0` tag that you created and pushed in Part 1 of this guide.

37995
dist/index.js vendored

File diff suppressed because one or more lines are too long

BIN
dist/unzip vendored Normal file

Binary file not shown.

View File

@@ -1,4 +1,4 @@
# Misc notes for contributors # Contributors
### Checkin ### Checkin

326
lib/installer.js generated
View File

@@ -1,57 +1,20 @@
"use strict"; "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (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 () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 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) { return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 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 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); } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next()); step((generator = generator.apply(thisArg, _arguments || [])).next());
}); });
}; };
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.getJuliaVersionInfo = getJuliaVersionInfo;
exports.getJuliaVersions = getJuliaVersions;
exports.getProjectFilePath = getProjectFilePath;
exports.validJuliaCompatRange = validJuliaCompatRange;
exports.readJuliaCompatRange = readJuliaCompatRange;
exports.getJuliaVersion = getJuliaVersion;
exports.getFileInfo = getFileInfo;
exports.getDownloadURL = getDownloadURL;
exports.installJulia = installJulia;
exports.showVersionInfo = showVersionInfo;
const core = __importStar(require("@actions/core")); const core = __importStar(require("@actions/core"));
const exec = __importStar(require("@actions/exec")); const exec = __importStar(require("@actions/exec"));
const tc = __importStar(require("@actions/tool-cache")); const tc = __importStar(require("@actions/tool-cache"));
@@ -61,9 +24,6 @@ const os = __importStar(require("os"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const retry = require("async-retry"); const retry = require("async-retry");
const semver = __importStar(require("semver")); const semver = __importStar(require("semver"));
const toml = __importStar(require("toml"));
const LTS_VERSION = '1.10';
const MAJOR_VERSION = '1'; // Could be deduced from versions.json
// Translations between actions input and Julia arch names // Translations between actions input and Julia arch names
const osMap = { const osMap = {
'win32': 'winnt', 'win32': 'winnt',
@@ -106,14 +66,14 @@ function getJuliaVersionInfo() {
const versionsFile = yield retry((bail) => __awaiter(this, void 0, void 0, function* () { const versionsFile = yield retry((bail) => __awaiter(this, void 0, void 0, function* () {
return yield tc.downloadTool('https://julialang-s3.julialang.org/bin/versions.json'); return yield tc.downloadTool('https://julialang-s3.julialang.org/bin/versions.json');
}), { }), {
retries: 5,
onRetry: (err) => { onRetry: (err) => {
core.info(`Download of versions.json failed, trying again. Error: ${err}`); core.debug(`Download of versions.json failed, trying again. Error: ${err}`);
} }
}); });
return JSON.parse(fs.readFileSync(versionsFile).toString()); return JSON.parse(fs.readFileSync(versionsFile).toString());
}); });
} }
exports.getJuliaVersionInfo = getJuliaVersionInfo;
/** /**
* @returns An array of all Julia versions available for download * @returns An array of all Julia versions available for download
*/ */
@@ -126,245 +86,60 @@ function getJuliaVersions(versionInfo) {
return versions; return versions;
}); });
} }
/** exports.getJuliaVersions = getJuliaVersions;
* @returns The path to the Julia project file function getJuliaVersion(availableReleases, versionInput) {
*/
function getProjectFilePath(projectInput = "") {
let projectFilePath = "";
// Default value for projectInput
if (!projectInput) {
projectInput = process.env.JULIA_PROJECT || ".";
}
if (fs.existsSync(projectInput) && fs.lstatSync(projectInput).isFile()) {
projectFilePath = projectInput;
}
else {
for (let projectFilename of ["JuliaProject.toml", "Project.toml"]) {
let p = path.join(projectInput, projectFilename);
if (fs.existsSync(p) && fs.lstatSync(p).isFile()) {
projectFilePath = p;
break;
}
}
}
if (!projectFilePath) {
throw new Error(`Unable to locate project file with project input: ${projectInput}`);
}
return projectFilePath;
}
/**
* @returns A valid NPM semver range from a Julia compat range or null if it's not valid
*/
function validJuliaCompatRange(compatRange) {
let ranges = [];
for (let range of compatRange.split(",")) {
range = range.trim();
// An empty range isn't supported by Julia
if (!range) {
return null;
}
// NPM's semver doesn't understand unicode characters such as `≥` so we'll convert to alternatives
range = range.replace("≥", ">=").replace("≤", "<=");
// Cleanup whitespace. Julia only allows whitespace between the specifier and version with certain specifiers
range = range.replace(/\s+/g, " ").replace(/(?<=(>|>=|≥|<)) (?=\d)/g, "");
if (!semver.validRange(range) || range.split(/(?<! -) (?!- )/).length > 1 || range.startsWith("<=") || range === "*") {
return null;
}
else if (range.search(/^\d/) === 0 && !range.includes(" ")) {
// Compat version is just a basic version number (e.g. 1.2.3). Since Julia's Pkg.jl's uses caret
// as the default specifier (e.g. `1.2.3 == ^1.2.3`) and NPM's semver uses tilde as the default
// specifier (e.g. `1.2.3 == 1.2.x == ~1.2.3`) we will introduce the caret specifier to ensure the
// orignal intent is respected.
// https://pkgdocs.julialang.org/v1/compatibility/#Version-specifier-format
// https://github.com/npm/node-semver#x-ranges-12x-1x-12-
range = "^" + range;
}
ranges.push(range);
}
return semver.validRange(ranges.join(" || "));
}
/**
* @returns An array of version ranges compatible with the Julia project
*/
function readJuliaCompatRange(projectFileContent) {
var _a;
let compatRange;
let meta = toml.parse(projectFileContent);
if (((_a = meta.compat) === null || _a === void 0 ? void 0 : _a.julia) !== undefined) {
compatRange = validJuliaCompatRange(meta.compat.julia);
}
else {
compatRange = "*";
}
if (!compatRange) {
throw new Error(`Invalid version range found in Julia compat: ${compatRange}`);
}
return compatRange;
}
function getJuliaVersion(availableReleases, versionInput, includePrerelease = false, juliaCompatRange = "") {
// Note: `juliaCompatRange` is ignored unless `versionInput` is `min`
let version;
if (semver.valid(versionInput) == versionInput || versionInput.endsWith('nightly')) { if (semver.valid(versionInput) == versionInput || versionInput.endsWith('nightly')) {
// versionInput is a valid version or a nightly version, use it directly // versionInput is a valid version or a nightly version, use it directly
version = versionInput; return versionInput;
} }
else if (versionInput == "min") { // Use the highest available version that matches versionInput
// Resolve "min" to the minimum supported Julia version compatible with the project file let version = semver.maxSatisfying(availableReleases, versionInput);
if (!juliaCompatRange) { if (version == null) {
throw new Error('Unable to use version "min" when the Julia project file does not specify a compat for Julia');
}
version = semver.minSatisfying(availableReleases, juliaCompatRange, { includePrerelease });
}
else if (versionInput == "lts") {
version = semver.maxSatisfying(availableReleases, LTS_VERSION, { includePrerelease: false });
}
else if (versionInput == "pre") {
version = semver.maxSatisfying(availableReleases, MAJOR_VERSION, { includePrerelease: true });
}
else {
// Use the highest available version that matches versionInput
version = semver.maxSatisfying(availableReleases, versionInput, { includePrerelease });
}
if (!version) {
throw new Error(`Could not find a Julia version that matches ${versionInput}`); throw new Error(`Could not find a Julia version that matches ${versionInput}`);
} }
// GitHub tags start with v, remove it // GitHub tags start with v, remove it
version = version.replace(/^v/, ''); version = version.replace(/^v/, '');
return version; return version;
} }
function getDesiredFileExts() { exports.getJuliaVersion = getJuliaVersion;
let fileExt1;
let hasFileExt2;
let fileExt2;
if (osPlat == 'win32') {
fileExt1 = 'tar.gz';
hasFileExt2 = true;
fileExt2 = 'exe';
}
else if (osPlat == 'darwin') {
fileExt1 = 'tar.gz';
hasFileExt2 = true;
fileExt2 = 'dmg';
}
else if (osPlat === 'linux') {
fileExt1 = 'tar.gz';
hasFileExt2 = false;
fileExt2 = '';
}
else {
throw new Error(`Platform ${osPlat} is not supported`);
}
return [fileExt1, hasFileExt2, fileExt2];
}
function getNightlyFileName(arch) { function getNightlyFileName(arch) {
let versionExt; let versionExt, ext;
let fileExt1;
[fileExt1, ,] = getDesiredFileExts();
if (osPlat == 'win32') { if (osPlat == 'win32') {
if (arch == 'x86') { versionExt = arch == 'x64' ? '-win64' : '-win32';
versionExt = '-win32'; ext = 'exe';
}
else if (arch == 'aarch64') {
throw new Error('Aarch64 Julia is not available on Windows');
}
else if (arch == 'x64') {
versionExt = '-win64';
}
else {
throw new Error(`Architecture ${arch} is not supported on Windows`);
}
} }
else if (osPlat == 'darwin') { else if (osPlat == 'darwin') {
if (arch == 'x86') { if (arch == 'x86') {
throw new Error('32-bit (x86) Julia is not available on macOS'); throw new Error('32-bit Julia is not available on macOS');
}
else if (arch == 'aarch64') {
versionExt = '-macaarch64';
}
else if (arch == 'x64') {
versionExt = '-mac64';
}
else {
throw new Error(`Architecture ${arch} is not supported on macOS`);
} }
versionExt = '-mac64';
ext = 'dmg';
} }
else if (osPlat === 'linux') { else if (osPlat === 'linux') {
if (arch == 'x86') { versionExt = arch == 'x64' ? '-linux64' : '-linux32';
versionExt = '-linux32'; ext = 'tar.gz';
}
else if (arch == 'aarch64') {
versionExt = '-linux-aarch64';
}
else if (arch == 'x64') {
versionExt = '-linux64';
}
else {
throw new Error(`Architecture ${arch} is not supported on Linux`);
}
} }
else { else {
throw new Error(`Platform ${osPlat} is not supported`); throw new Error(`Platform ${osPlat} is not supported`);
} }
return `julia-latest${versionExt}.${fileExt1}`; return `julia-latest${versionExt}.${ext}`;
} }
function getFileInfo(versionInfo, version, arch) { function getFileInfo(versionInfo, version, arch) {
const err = `Could not find ${archMap[arch]}/${version} binaries`; const err = `Could not find ${archMap[arch]}/${version} binaries`;
let fileExt1;
let hasFileExt2;
let fileExt2;
[fileExt1, hasFileExt2, fileExt2] = getDesiredFileExts();
if (version.endsWith('nightly')) { if (version.endsWith('nightly')) {
return null; return null;
} }
if (!versionInfo[version]) { if (!versionInfo[version]) {
core.error(`Encountered error: ${err}`);
throw err; throw err;
} }
for (let file of versionInfo[version].files) { for (let file of versionInfo[version].files) {
if (file.os == osMap[osPlat] && file.arch == archMap[arch]) { if (file.os == osMap[osPlat] && file.arch == archMap[arch]) {
if (file.extension == fileExt1) { return file;
return file;
}
} }
} }
if (hasFileExt2) {
core.debug(`Could not find ${fileExt1}; trying to find ${fileExt2} instead`);
for (let file of versionInfo[version].files) {
if (file.os == osMap[osPlat] && file.arch == archMap[arch]) {
if (file.extension == fileExt2) {
return file;
}
}
}
// The following block is just to provide improved log messages in the CI logs.
// We specifically want to improve the case where someone is trying to install
// Julia 1.6 or 1.7 on Apple Silicon (aarch64) macOS.
{
const one_fileext_is_targz = (fileExt1 == "tar.gz") || (fileExt2 == "tar.gz");
const one_fileext_is_dmg = (fileExt1 == "dmg") || (fileExt2 == "dmg");
const one_fileext_is_targz_and_other_is_dmg = one_fileext_is_targz && one_fileext_is_dmg;
// We say that "this Julia version does NOT have native binaries for Apple Silicon"
// if and only if "this Julia version is < 1.8.0"
const this_julia_version_does_NOT_have_native_binaries_for_apple_silicon = semver.lt(version, '1.8.0');
const this_is_macos = osPlat == 'darwin';
if (this_is_macos && one_fileext_is_targz_and_other_is_dmg && this_julia_version_does_NOT_have_native_binaries_for_apple_silicon) {
const msg = `It looks like you are trying to install Julia 1.6 or 1.7 on ` +
`the "macos-latest" runners.\n` +
`"macos-latest" now resolves to "macos-14", which run on Apple ` +
`Silicon (aarch64) macOS machines.\n` +
`Unfortunately, Julia 1.6 and 1.7 do not have native binaries ` +
`available for Apple Silicon macOS.\n` +
`Therefore, it is not possible to install Julia with the current ` +
`constraints.\n` +
`For instructions on how to fix this error, please see the following Discourse post: ` +
`https://discourse.julialang.org/t/how-to-fix-github-actions-ci-failures-with-julia-1-6-or-1-7-on-macos-latest-and-macos-14/117019`;
core.error(msg);
}
}
}
core.error(`Encountered error: ${err}`);
throw err; throw err;
} }
exports.getFileInfo = getFileInfo;
function getDownloadURL(fileInfo, version, arch) { function getDownloadURL(fileInfo, version, arch) {
const baseURL = `https://julialangnightlies-s3.julialang.org/bin/${osMap[osPlat]}/${arch}`; const baseURL = `https://julialangnightlies-s3.julialang.org/bin/${osMap[osPlat]}/${arch}`;
// release branch nightlies, e.g. 1.6-nightlies should return .../bin/linux/x64/1.6/julia-latest-linux64.tar.gz // release branch nightlies, e.g. 1.6-nightlies should return .../bin/linux/x64/1.6/julia-latest-linux64.tar.gz
@@ -382,7 +157,8 @@ function getDownloadURL(fileInfo, version, arch) {
} }
return fileInfo.url; return fileInfo.url;
} }
function installJulia(dest, versionInfo, version, arch) { exports.getDownloadURL = getDownloadURL;
function installJulia(versionInfo, version, arch) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
// Download Julia // Download Julia
const fileInfo = getFileInfo(versionInfo, version, arch); const fileInfo = getFileInfo(versionInfo, version, arch);
@@ -393,9 +169,8 @@ function installJulia(dest, versionInfo, version, arch) {
const juliaDownloadPath = yield retry((bail) => __awaiter(this, void 0, void 0, function* () { const juliaDownloadPath = yield retry((bail) => __awaiter(this, void 0, void 0, function* () {
return yield tc.downloadTool(downloadURL); return yield tc.downloadTool(downloadURL);
}), { }), {
retries: 5,
onRetry: (err) => { onRetry: (err) => {
core.info(`Download of ${downloadURL} failed, trying again. Error: ${err}`); core.debug(`Download of ${downloadURL} failed, trying again. Error: ${err}`);
} }
}); });
// Verify checksum // Verify checksum
@@ -409,46 +184,32 @@ function installJulia(dest, versionInfo, version, arch) {
else { else {
core.debug('Skipping checksum check for nightly binaries.'); core.debug('Skipping checksum check for nightly binaries.');
} }
const tempInstallDir = fs.mkdtempSync(`julia-${arch}-${version}-`);
// Install it // Install it
switch (osPlat) { switch (osPlat) {
case 'linux': case 'linux':
// tc.extractTar doesn't support stripping components, so we have to call tar manually // tc.extractTar doesn't support stripping components, so we have to call tar manually
yield exec.exec('tar', ['xf', juliaDownloadPath, '--strip-components=1', '-C', dest]); yield exec.exec('tar', ['xf', juliaDownloadPath, '--strip-components=1', '-C', tempInstallDir]);
return dest; return tempInstallDir;
case 'win32': case 'win32':
if (fileInfo !== null && fileInfo.extension == 'exe') { if (version.endsWith('nightly') || semver.gtr(version, '1.3', { includePrerelease: true })) {
if (version.endsWith('nightly') || semver.gtr(version, '1.3', { includePrerelease: true })) { // The installer changed in 1.4: https://github.com/JuliaLang/julia/blob/ef0c9108b12f3ae177c51037934351ffa703b0b5/NEWS.md#build-system-changes
// The installer changed in 1.4: https://github.com/JuliaLang/julia/blob/ef0c9108b12f3ae177c51037934351ffa703b0b5/NEWS.md#build-system-changes yield exec.exec('powershell', ['-Command', `Start-Process -FilePath ${juliaDownloadPath} -ArgumentList "/SILENT /dir=${path.join(process.cwd(), tempInstallDir)}" -NoNewWindow -Wait`]);
yield exec.exec('powershell', ['-Command', `Start-Process -FilePath ${juliaDownloadPath} -ArgumentList "/SILENT /dir=${path.join(process.cwd(), dest)}" -NoNewWindow -Wait`]);
}
else {
yield exec.exec('powershell', ['-Command', `Start-Process -FilePath ${juliaDownloadPath} -ArgumentList "/S /D=${path.join(process.cwd(), dest)}" -NoNewWindow -Wait`]);
}
} }
else { else {
// This is the more common path. Using .tar.gz is much faster yield exec.exec('powershell', ['-Command', `Start-Process -FilePath ${juliaDownloadPath} -ArgumentList "/S /D=${path.join(process.cwd(), tempInstallDir)}" -NoNewWindow -Wait`]);
// don't use the Git bash provided tar. Issue #205
// https://github.com/julia-actions/setup-julia/issues/205
yield exec.exec('powershell', ['-Command', `& "$env:WINDIR/System32/tar" xf ${juliaDownloadPath} --strip-components=1 -C ${dest}`]);
} }
return dest; return tempInstallDir;
case 'darwin': case 'darwin':
if (fileInfo !== null && fileInfo.extension == 'dmg') { yield exec.exec('hdiutil', ['attach', juliaDownloadPath]);
core.debug(`Support for .dmg files is deprecated and may be removed in a future release`); yield exec.exec('/bin/bash', ['-c', `cp -a /Volumes/Julia-*/Julia-*.app/Contents/Resources/julia ${tempInstallDir}`]);
yield exec.exec('hdiutil', ['attach', juliaDownloadPath]); return path.join(tempInstallDir, 'julia');
yield exec.exec('/bin/bash', ['-c', `cp -a /Volumes/Julia-*/Julia-*.app/Contents/Resources/julia ${dest}`]);
return path.join(dest, 'julia');
}
else {
// tc.extractTar doesn't support stripping components, so we have to call tar manually
yield exec.exec('tar', ['xf', juliaDownloadPath, '--strip-components=1', '-C', dest]);
return dest;
}
default: default:
throw new Error(`Platform ${osPlat} is not supported`); throw new Error(`Platform ${osPlat} is not supported`);
} }
}); });
} }
exports.installJulia = installJulia;
/** /**
* Test if Julia has been installed and print the version. * Test if Julia has been installed and print the version.
* *
@@ -485,3 +246,4 @@ function showVersionInfo(showVersionInfoInput, version) {
} }
}); });
} }
exports.showVersionInfo = showVersionInfo;

112
lib/setup-julia.js generated
View File

@@ -1,65 +1,26 @@
"use strict"; "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (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 () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 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) { return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 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 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); } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next()); step((generator = generator.apply(thisArg, _arguments || [])).next());
}); });
}; };
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core")); const core = __importStar(require("@actions/core"));
const tc = __importStar(require("@actions/tool-cache")); const tc = __importStar(require("@actions/tool-cache"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const https = __importStar(require("https")); const https = __importStar(require("https"));
const os = __importStar(require("os"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const installer = __importStar(require("./installer")); const installer = __importStar(require("./installer"));
// Note: before we index into this dict, we always first do `.toLowerCase()` on
// the key.
//
// Therefore, this dict does not need to account for differences in case.
const archSynonyms = {
'x86': 'x86',
'x64': 'x64',
'x86_64': 'x64',
'aarch64': 'aarch64',
'arm64': 'aarch64'
};
function run() { function run() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
@@ -78,52 +39,23 @@ function run() {
core.debug(`ERROR: Could not retrieve runner IP: ${err}`); core.debug(`ERROR: Could not retrieve runner IP: ${err}`);
}); });
} }
// Inputs. // Inputs
// Note that we intentionally strip leading and lagging whitespace by using `.trim()` const versionInput = core.getInput('version');
const versionInput = core.getInput('version').trim(); const arch = core.getInput('arch');
const includePrereleases = core.getInput('include-all-prereleases').trim() == 'true';
const originalArchInput = core.getInput('arch').trim();
const projectInput = core.getInput('project').trim(); // Julia project file
// It can easily happen that, for example, a workflow file contains an input `version: ${{ matrix.julia-version }}` // It can easily happen that, for example, a workflow file contains an input `version: ${{ matrix.julia-version }}`
// while the strategy matrix only contains a key `${{ matrix.version }}`. // while the strategy matrix only contains a key `${{ matrix.version }}`.
// In that case, we want the action to fail, rather than trying to download julia from an URL that's missing parts and 404ing. // In that case, we want the action to fail, rather than trying to download julia from an URL that's missing parts and 404ing.
// We _could_ fall back to the default but that means that builds silently do things differently than they're meant to, which // We _could_ fall back to the default but that means that builds silently do things differently than they're meant to, which
// is worse than failing the build. // is worse than failing the build.
if (!versionInput) { // if `versionInput` is an empty string if (!versionInput) {
throw new Error('Version input must not be null'); throw new Error('Version input must not be null');
} }
if (versionInput == '1.6') { if (!arch) {
core.notice('[setup-julia] If you are testing 1.6 as a Long Term Support (lts) version, consider using the new "lts" version specifier instead of "1.6" explicitly, which will automatically resolve the current lts.');
}
if (!originalArchInput) { // if `originalArchInput` is an empty string
throw new Error(`Arch input must not be null`); throw new Error(`Arch input must not be null`);
} }
if (originalArchInput == 'x64' && os.platform() == 'darwin' && os.arch() == 'arm64') {
core.warning('[setup-julia] x64 arch has been requested on a macOS runner that has an arm64 (Apple Silicon) architecture. You may have meant to use the "aarch64" arch instead (or left it unspecified for the correct default).');
}
let processedArchInput;
if (originalArchInput == "default") {
// If the user sets the `arch` input to `default`, then we use the
// architecture of the machine that we are running on.
processedArchInput = os.arch();
core.debug(`The "arch" input is "default", so we will use the machine arch: ${processedArchInput}`);
}
else {
processedArchInput = originalArchInput;
}
// Note: we convert the key `processedArchInput` to lower case
// before we index into the `archSynonyms` dict.
const arch = archSynonyms[processedArchInput.toLowerCase()];
core.debug(`Mapped the "arch" from ${processedArchInput} to ${arch}`);
// Determine the Julia compat ranges as specified by the Project.toml only for special versions that require them.
let juliaCompatRange = "";
if (versionInput === "min") {
const projectFilePath = installer.getProjectFilePath(projectInput);
juliaCompatRange = installer.readJuliaCompatRange(fs.readFileSync(projectFilePath).toString());
}
const versionInfo = yield installer.getJuliaVersionInfo(); const versionInfo = yield installer.getJuliaVersionInfo();
const availableReleases = yield installer.getJuliaVersions(versionInfo); const availableReleases = yield installer.getJuliaVersions(versionInfo);
const version = installer.getJuliaVersion(availableReleases, versionInput, includePrereleases, juliaCompatRange); const version = installer.getJuliaVersion(availableReleases, versionInput);
core.debug(`selected Julia version: ${arch}/${version}`); core.debug(`selected Julia version: ${arch}/${version}`);
core.setOutput('julia-version', version); core.setOutput('julia-version', version);
// Search in cache // Search in cache
@@ -131,18 +63,12 @@ function run() {
juliaPath = tc.find('julia', version, arch); juliaPath = tc.find('julia', version, arch);
if (!juliaPath) { if (!juliaPath) {
core.debug(`could not find Julia ${arch}/${version} in cache`); core.debug(`could not find Julia ${arch}/${version} in cache`);
// https://github.com/julia-actions/setup-julia/pull/196 const juliaInstallationPath = yield installer.installJulia(versionInfo, version, arch);
// we want julia to be installed with unmodified file mtimes // Add it to cache
// but `tc.cacheDir` uses `cp` internally which destroys mtime juliaPath = yield tc.cacheDir(juliaInstallationPath, 'julia', version, arch);
// and `tc` provides no API to get the tool directory alone
// so hack it by installing a empty directory then use the path it returns
// and extract the archives directly to that location
const emptyDir = fs.mkdtempSync('empty');
juliaPath = yield tc.cacheDir(emptyDir, 'julia', version, arch);
yield installer.installJulia(juliaPath, versionInfo, version, arch);
core.debug(`added Julia to cache: ${juliaPath}`); core.debug(`added Julia to cache: ${juliaPath}`);
// Remove empty dir // Remove temporary dir
fs.rmdirSync(emptyDir); fs.rmdirSync(juliaInstallationPath, { recursive: true });
} }
else { else {
core.debug(`using cached version of Julia: ${juliaPath}`); core.debug(`using cached version of Julia: ${juliaPath}`);

17090
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
{ {
"name": "setup-julia", "name": "setup-julia",
"version": "1.7.0",
"private": true, "private": true,
"description": "setup Julia action", "description": "setup Julia action",
"main": "lib/setup-julia.js", "main": "lib/setup-julia.js",
@@ -20,26 +21,24 @@
"author": "Sascha Mann <git@mail.saschamann.eu>", "author": "Sascha Mann <git@mail.saschamann.eu>",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.11.1", "@actions/core": "^1.2.6",
"@actions/exec": "^1.1.1", "@actions/exec": "^1.0.0",
"@actions/io": "^1.1.3", "@actions/io": "^1.0.0",
"@actions/tool-cache": "^2.0.2", "@actions/tool-cache": "^1.0.0",
"async-retry": "^1.3.3", "async-retry": "^1.3.1",
"semver": "^7.7.0", "semver": "^6.3.0"
"toml": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/async-retry": "^1.4.9", "@types/async-retry": "^1.4.2",
"@types/jest": "^30.0.0", "@types/jest": "^24.0.13",
"@types/node": "^25.2.0", "@types/node": "^12.12.7",
"@types/retry": "^0.12.5", "@types/semver": "^6.0.0",
"@types/semver": "^7.7.0", "@zeit/ncc": "^0.22.0",
"@vercel/ncc": "^0.38.3", "jest": "^24.8.0",
"jest": "^30.0.5", "jest-circus": "^24.7.1",
"jest-circus": "^30.0.3", "nock": "^11.7.2",
"nock": "^14.0.8", "prettier": "^1.17.1",
"prettier": "^3.6.2", "ts-jest": "^26.0.0",
"ts-jest": "^29.4.0", "typescript": "^3.5.1"
"typescript": "^5.9.2"
} }
} }

View File

@@ -9,10 +9,6 @@ import * as path from 'path'
import retry = require('async-retry') import retry = require('async-retry')
import * as semver from 'semver' import * as semver from 'semver'
import * as toml from 'toml'
const LTS_VERSION = '1.10'
const MAJOR_VERSION = '1' // Could be deduced from versions.json
// Translations between actions input and Julia arch names // Translations between actions input and Julia arch names
const osMap = { const osMap = {
@@ -58,9 +54,8 @@ export async function getJuliaVersionInfo(): Promise<object> {
const versionsFile = await retry(async (bail: Function) => { const versionsFile = await retry(async (bail: Function) => {
return await tc.downloadTool('https://julialang-s3.julialang.org/bin/versions.json') return await tc.downloadTool('https://julialang-s3.julialang.org/bin/versions.json')
}, { }, {
retries: 5,
onRetry: (err: Error) => { onRetry: (err: Error) => {
core.info(`Download of versions.json failed, trying again. Error: ${err}`) core.debug(`Download of versions.json failed, trying again. Error: ${err}`)
} }
}) })
@@ -80,116 +75,15 @@ export async function getJuliaVersions(versionInfo): Promise<string[]> {
return versions return versions
} }
/** export function getJuliaVersion(availableReleases: string[], versionInput: string): string {
* @returns The path to the Julia project file
*/
export function getProjectFilePath(projectInput: string = ""): string {
let projectFilePath: string = ""
// Default value for projectInput
if (!projectInput) {
projectInput = process.env.JULIA_PROJECT || "."
}
if (fs.existsSync(projectInput) && fs.lstatSync(projectInput).isFile()) {
projectFilePath = projectInput
} else {
for (let projectFilename of ["JuliaProject.toml", "Project.toml"]) {
let p = path.join(projectInput, projectFilename)
if (fs.existsSync(p) && fs.lstatSync(p).isFile()) {
projectFilePath = p
break
}
}
}
if (!projectFilePath) {
throw new Error(`Unable to locate project file with project input: ${projectInput}`)
}
return projectFilePath
}
/**
* @returns A valid NPM semver range from a Julia compat range or null if it's not valid
*/
export function validJuliaCompatRange(compatRange: string): string | null {
let ranges: Array<string> = []
for(let range of compatRange.split(",")) {
range = range.trim()
// An empty range isn't supported by Julia
if (!range) {
return null
}
// NPM's semver doesn't understand unicode characters such as `≥` so we'll convert to alternatives
range = range.replace("≥", ">=").replace("≤", "<=")
// Cleanup whitespace. Julia only allows whitespace between the specifier and version with certain specifiers
range = range.replace(/\s+/g, " ").replace(/(?<=(>|>=|≥|<)) (?=\d)/g, "")
if (!semver.validRange(range) || range.split(/(?<! -) (?!- )/).length > 1 || range.startsWith("<=") || range === "*") {
return null
} else if (range.search(/^\d/) === 0 && !range.includes(" ")) {
// Compat version is just a basic version number (e.g. 1.2.3). Since Julia's Pkg.jl's uses caret
// as the default specifier (e.g. `1.2.3 == ^1.2.3`) and NPM's semver uses tilde as the default
// specifier (e.g. `1.2.3 == 1.2.x == ~1.2.3`) we will introduce the caret specifier to ensure the
// orignal intent is respected.
// https://pkgdocs.julialang.org/v1/compatibility/#Version-specifier-format
// https://github.com/npm/node-semver#x-ranges-12x-1x-12-
range = "^" + range
}
ranges.push(range)
}
return semver.validRange(ranges.join(" || "))
}
/**
* @returns An array of version ranges compatible with the Julia project
*/
export function readJuliaCompatRange(projectFileContent: string): string {
let compatRange: string | null
let meta = toml.parse(projectFileContent)
if (meta.compat?.julia !== undefined) {
compatRange = validJuliaCompatRange(meta.compat.julia)
} else {
compatRange = "*"
}
if (!compatRange) {
throw new Error(`Invalid version range found in Julia compat: ${compatRange}`)
}
return compatRange
}
export function getJuliaVersion(availableReleases: string[], versionInput: string, includePrerelease: boolean = false, juliaCompatRange: string = ""): string {
// Note: `juliaCompatRange` is ignored unless `versionInput` is `min`
let version: string | null
if (semver.valid(versionInput) == versionInput || versionInput.endsWith('nightly')) { if (semver.valid(versionInput) == versionInput || versionInput.endsWith('nightly')) {
// versionInput is a valid version or a nightly version, use it directly // versionInput is a valid version or a nightly version, use it directly
version = versionInput return versionInput
} else if (versionInput == "min") {
// Resolve "min" to the minimum supported Julia version compatible with the project file
if (!juliaCompatRange) {
throw new Error('Unable to use version "min" when the Julia project file does not specify a compat for Julia')
}
version = semver.minSatisfying(availableReleases, juliaCompatRange, {includePrerelease})
} else if (versionInput == "lts") {
version = semver.maxSatisfying(availableReleases, LTS_VERSION, { includePrerelease: false });
} else if (versionInput == "pre") {
version = semver.maxSatisfying(availableReleases, MAJOR_VERSION, { includePrerelease: true });
} else {
// Use the highest available version that matches versionInput
version = semver.maxSatisfying(availableReleases, versionInput, {includePrerelease})
} }
if (!version) { // Use the highest available version that matches versionInput
let version = semver.maxSatisfying(availableReleases, versionInput)
if (version == null) {
throw new Error(`Could not find a Julia version that matches ${versionInput}`) throw new Error(`Could not find a Julia version that matches ${versionInput}`)
} }
@@ -199,139 +93,45 @@ export function getJuliaVersion(availableReleases: string[], versionInput: strin
return version return version
} }
function getDesiredFileExts(): [string, boolean, string] {
let fileExt1: string
let hasFileExt2: boolean
let fileExt2: string
if (osPlat == 'win32') {
fileExt1 = 'tar.gz'
hasFileExt2 = true
fileExt2 = 'exe'
} else if (osPlat == 'darwin') {
fileExt1 = 'tar.gz'
hasFileExt2 = true
fileExt2 = 'dmg'
} else if (osPlat === 'linux') {
fileExt1 = 'tar.gz'
hasFileExt2 = false
fileExt2 = ''
} else {
throw new Error(`Platform ${osPlat} is not supported`)
}
return [fileExt1, hasFileExt2, fileExt2]
}
function getNightlyFileName(arch: string): string { function getNightlyFileName(arch: string): string {
let versionExt: string let versionExt: string, ext: string
let fileExt1: string
[fileExt1, , ] = getDesiredFileExts()
if (osPlat == 'win32') { if (osPlat == 'win32') {
if (arch == 'x86') { versionExt = arch == 'x64' ? '-win64' : '-win32'
versionExt = '-win32' ext = 'exe'
} else if (arch == 'aarch64') {
throw new Error('Aarch64 Julia is not available on Windows')
} else if (arch == 'x64') {
versionExt = '-win64'
} else {
throw new Error(`Architecture ${arch} is not supported on Windows`)
}
} else if (osPlat == 'darwin') { } else if (osPlat == 'darwin') {
if (arch == 'x86') { if (arch == 'x86') {
throw new Error('32-bit (x86) Julia is not available on macOS') throw new Error('32-bit Julia is not available on macOS')
} else if (arch == 'aarch64') {
versionExt = '-macaarch64'
} else if (arch == 'x64') {
versionExt = '-mac64'
} else {
throw new Error(`Architecture ${arch} is not supported on macOS`)
} }
versionExt = '-mac64'
ext = 'dmg'
} else if (osPlat === 'linux') { } else if (osPlat === 'linux') {
if (arch == 'x86') { versionExt = arch == 'x64' ? '-linux64' : '-linux32'
versionExt = '-linux32' ext = 'tar.gz'
} else if (arch == 'aarch64') {
versionExt = '-linux-aarch64'
} else if (arch == 'x64') {
versionExt = '-linux64'
} else {
throw new Error(`Architecture ${arch} is not supported on Linux`)
}
} else { } else {
throw new Error(`Platform ${osPlat} is not supported`) throw new Error(`Platform ${osPlat} is not supported`)
} }
return `julia-latest${versionExt}.${fileExt1}` return `julia-latest${versionExt}.${ext}`
} }
export function getFileInfo(versionInfo, version: string, arch: string) { export function getFileInfo(versionInfo, version: string, arch: string) {
const err = `Could not find ${archMap[arch]}/${version} binaries` const err = `Could not find ${archMap[arch]}/${version} binaries`
let fileExt1: string
let hasFileExt2: boolean
let fileExt2: string
[fileExt1, hasFileExt2, fileExt2] = getDesiredFileExts()
if (version.endsWith('nightly')) { if (version.endsWith('nightly')) {
return null return null
} }
if (!versionInfo[version]) { if (!versionInfo[version]) {
core.error(`Encountered error: ${err}`)
throw err throw err
} }
for (let file of versionInfo[version].files) { for (let file of versionInfo[version].files) {
if (file.os == osMap[osPlat] && file.arch == archMap[arch]) { if (file.os == osMap[osPlat] && file.arch == archMap[arch]) {
if (file.extension == fileExt1) { return file
return file
}
} }
} }
if (hasFileExt2) {
core.debug(`Could not find ${fileExt1}; trying to find ${fileExt2} instead`)
for (let file of versionInfo[version].files) {
if (file.os == osMap[osPlat] && file.arch == archMap[arch]) {
if (file.extension == fileExt2) {
return file
}
}
}
// The following block is just to provide improved log messages in the CI logs.
// We specifically want to improve the case where someone is trying to install
// Julia 1.6 or 1.7 on Apple Silicon (aarch64) macOS.
{
const one_fileext_is_targz = (fileExt1 == "tar.gz") || (fileExt2 == "tar.gz");
const one_fileext_is_dmg = (fileExt1 == "dmg") || (fileExt2 == "dmg");
const one_fileext_is_targz_and_other_is_dmg = one_fileext_is_targz && one_fileext_is_dmg;
// We say that "this Julia version does NOT have native binaries for Apple Silicon"
// if and only if "this Julia version is < 1.8.0"
const this_julia_version_does_NOT_have_native_binaries_for_apple_silicon = semver.lt(
version,
'1.8.0',
);
const this_is_macos = osPlat == 'darwin';
if (this_is_macos && one_fileext_is_targz_and_other_is_dmg && this_julia_version_does_NOT_have_native_binaries_for_apple_silicon) {
const msg = `It looks like you are trying to install Julia 1.6 or 1.7 on ` +
`the "macos-latest" runners.\n` +
`"macos-latest" now resolves to "macos-14", which run on Apple ` +
`Silicon (aarch64) macOS machines.\n` +
`Unfortunately, Julia 1.6 and 1.7 do not have native binaries ` +
`available for Apple Silicon macOS.\n` +
`Therefore, it is not possible to install Julia with the current ` +
`constraints.\n` +
`For instructions on how to fix this error, please see the following Discourse post: ` +
`https://discourse.julialang.org/t/how-to-fix-github-actions-ci-failures-with-julia-1-6-or-1-7-on-macos-latest-and-macos-14/117019`
core.error(msg);
}
}
}
core.error(`Encountered error: ${err}`)
throw err throw err
} }
@@ -356,7 +156,7 @@ export function getDownloadURL(fileInfo, version: string, arch: string): string
return fileInfo.url return fileInfo.url
} }
export async function installJulia(dest: string, versionInfo, version: string, arch: string): Promise<string> { export async function installJulia(versionInfo, version: string, arch: string): Promise<string> {
// Download Julia // Download Julia
const fileInfo = getFileInfo(versionInfo, version, arch) const fileInfo = getFileInfo(versionInfo, version, arch)
const downloadURL = getDownloadURL(fileInfo, version, arch) const downloadURL = getDownloadURL(fileInfo, version, arch)
@@ -367,12 +167,12 @@ export async function installJulia(dest: string, versionInfo, version: string, a
const juliaDownloadPath = await retry(async (bail: Function) => { const juliaDownloadPath = await retry(async (bail: Function) => {
return await tc.downloadTool(downloadURL) return await tc.downloadTool(downloadURL)
}, { }, {
retries: 5,
onRetry: (err: Error) => { onRetry: (err: Error) => {
core.info(`Download of ${downloadURL} failed, trying again. Error: ${err}`) core.debug(`Download of ${downloadURL} failed, trying again. Error: ${err}`)
} }
}) })
// Verify checksum // Verify checksum
if (!version.endsWith('nightly')) { if (!version.endsWith('nightly')) {
const checkSum = await calculateChecksum(juliaDownloadPath) const checkSum = await calculateChecksum(juliaDownloadPath)
@@ -384,38 +184,26 @@ export async function installJulia(dest: string, versionInfo, version: string, a
core.debug('Skipping checksum check for nightly binaries.') core.debug('Skipping checksum check for nightly binaries.')
} }
const tempInstallDir = fs.mkdtempSync(`julia-${arch}-${version}-`)
// Install it // Install it
switch (osPlat) { switch (osPlat) {
case 'linux': case 'linux':
// tc.extractTar doesn't support stripping components, so we have to call tar manually // tc.extractTar doesn't support stripping components, so we have to call tar manually
await exec.exec('tar', ['xf', juliaDownloadPath, '--strip-components=1', '-C', dest]) await exec.exec('tar', ['xf', juliaDownloadPath, '--strip-components=1', '-C', tempInstallDir])
return dest return tempInstallDir
case 'win32': case 'win32':
if (fileInfo !== null && fileInfo.extension == 'exe') { if (version.endsWith('nightly') || semver.gtr(version, '1.3', {includePrerelease: true})) {
if (version.endsWith('nightly') || semver.gtr(version, '1.3', {includePrerelease: true})) { // The installer changed in 1.4: https://github.com/JuliaLang/julia/blob/ef0c9108b12f3ae177c51037934351ffa703b0b5/NEWS.md#build-system-changes
// The installer changed in 1.4: https://github.com/JuliaLang/julia/blob/ef0c9108b12f3ae177c51037934351ffa703b0b5/NEWS.md#build-system-changes await exec.exec('powershell', ['-Command', `Start-Process -FilePath ${juliaDownloadPath} -ArgumentList "/SILENT /dir=${path.join(process.cwd(), tempInstallDir)}" -NoNewWindow -Wait`])
await exec.exec('powershell', ['-Command', `Start-Process -FilePath ${juliaDownloadPath} -ArgumentList "/SILENT /dir=${path.join(process.cwd(), dest)}" -NoNewWindow -Wait`])
} else {
await exec.exec('powershell', ['-Command', `Start-Process -FilePath ${juliaDownloadPath} -ArgumentList "/S /D=${path.join(process.cwd(), dest)}" -NoNewWindow -Wait`])
}
} else { } else {
// This is the more common path. Using .tar.gz is much faster await exec.exec('powershell', ['-Command', `Start-Process -FilePath ${juliaDownloadPath} -ArgumentList "/S /D=${path.join(process.cwd(), tempInstallDir)}" -NoNewWindow -Wait`])
// don't use the Git bash provided tar. Issue #205
// https://github.com/julia-actions/setup-julia/issues/205
await exec.exec('powershell', ['-Command', `& "$env:WINDIR/System32/tar" xf ${juliaDownloadPath} --strip-components=1 -C ${dest}`])
} }
return dest return tempInstallDir
case 'darwin': case 'darwin':
if (fileInfo !== null && fileInfo.extension == 'dmg') { await exec.exec('hdiutil', ['attach', juliaDownloadPath])
core.debug(`Support for .dmg files is deprecated and may be removed in a future release`) await exec.exec('/bin/bash', ['-c', `cp -a /Volumes/Julia-*/Julia-*.app/Contents/Resources/julia ${tempInstallDir}`])
await exec.exec('hdiutil', ['attach', juliaDownloadPath]) return path.join(tempInstallDir, 'julia')
await exec.exec('/bin/bash', ['-c', `cp -a /Volumes/Julia-*/Julia-*.app/Contents/Resources/julia ${dest}`])
return path.join(dest, 'julia')
} else {
// tc.extractTar doesn't support stripping components, so we have to call tar manually
await exec.exec('tar', ['xf', juliaDownloadPath, '--strip-components=1', '-C', dest])
return dest
}
default: default:
throw new Error(`Platform ${osPlat} is not supported`) throw new Error(`Platform ${osPlat} is not supported`)
} }

View File

@@ -1,25 +1,13 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as exec from '@actions/exec'
import * as tc from '@actions/tool-cache' import * as tc from '@actions/tool-cache'
import * as fs from 'fs' import * as fs from 'fs'
import * as https from 'https' import * as https from 'https'
import * as os from 'os'
import * as path from 'path' import * as path from 'path'
import * as installer from './installer' import * as installer from './installer'
// Note: before we index into this dict, we always first do `.toLowerCase()` on
// the key.
//
// Therefore, this dict does not need to account for differences in case.
const archSynonyms = {
'x86': 'x86',
'x64': 'x64',
'x86_64': 'x64',
'aarch64': 'aarch64',
'arm64': 'aarch64'
}
async function run() { async function run() {
try { try {
// Debugging info // Debugging info
@@ -40,56 +28,25 @@ async function run() {
}) })
} }
// Inputs. // Inputs
// Note that we intentionally strip leading and lagging whitespace by using `.trim()` const versionInput = core.getInput('version')
const versionInput = core.getInput('version').trim() const arch = core.getInput('arch')
const includePrereleases = core.getInput('include-all-prereleases').trim() == 'true'
const originalArchInput = core.getInput('arch').trim()
const projectInput = core.getInput('project').trim() // Julia project file
// It can easily happen that, for example, a workflow file contains an input `version: ${{ matrix.julia-version }}` // It can easily happen that, for example, a workflow file contains an input `version: ${{ matrix.julia-version }}`
// while the strategy matrix only contains a key `${{ matrix.version }}`. // while the strategy matrix only contains a key `${{ matrix.version }}`.
// In that case, we want the action to fail, rather than trying to download julia from an URL that's missing parts and 404ing. // In that case, we want the action to fail, rather than trying to download julia from an URL that's missing parts and 404ing.
// We _could_ fall back to the default but that means that builds silently do things differently than they're meant to, which // We _could_ fall back to the default but that means that builds silently do things differently than they're meant to, which
// is worse than failing the build. // is worse than failing the build.
if (!versionInput) { // if `versionInput` is an empty string if (!versionInput) {
throw new Error('Version input must not be null') throw new Error('Version input must not be null')
} }
if (versionInput == '1.6') { if (!arch) {
core.notice('[setup-julia] If you are testing 1.6 as a Long Term Support (lts) version, consider using the new "lts" version specifier instead of "1.6" explicitly, which will automatically resolve the current lts.')
}
if (!originalArchInput) { // if `originalArchInput` is an empty string
throw new Error(`Arch input must not be null`) throw new Error(`Arch input must not be null`)
} }
if (originalArchInput == 'x64' && os.platform() == 'darwin' && os.arch() == 'arm64') {
core.warning('[setup-julia] x64 arch has been requested on a macOS runner that has an arm64 (Apple Silicon) architecture. You may have meant to use the "aarch64" arch instead (or left it unspecified for the correct default).')
}
let processedArchInput: string;
if (originalArchInput == "default") {
// If the user sets the `arch` input to `default`, then we use the
// architecture of the machine that we are running on.
processedArchInput = os.arch();
core.debug(`The "arch" input is "default", so we will use the machine arch: ${processedArchInput}`)
} else {
processedArchInput = originalArchInput;
}
// Note: we convert the key `processedArchInput` to lower case
// before we index into the `archSynonyms` dict.
const arch = archSynonyms[processedArchInput.toLowerCase()]
core.debug(`Mapped the "arch" from ${processedArchInput} to ${arch}`)
// Determine the Julia compat ranges as specified by the Project.toml only for special versions that require them.
let juliaCompatRange: string = "";
if (versionInput === "min") {
const projectFilePath = installer.getProjectFilePath(projectInput)
juliaCompatRange = installer.readJuliaCompatRange(fs.readFileSync(projectFilePath).toString())
}
const versionInfo = await installer.getJuliaVersionInfo() const versionInfo = await installer.getJuliaVersionInfo()
const availableReleases = await installer.getJuliaVersions(versionInfo) const availableReleases = await installer.getJuliaVersions(versionInfo)
const version = installer.getJuliaVersion(availableReleases, versionInput, includePrereleases, juliaCompatRange) const version = installer.getJuliaVersion(availableReleases, versionInput)
core.debug(`selected Julia version: ${arch}/${version}`) core.debug(`selected Julia version: ${arch}/${version}`)
core.setOutput('julia-version', version) core.setOutput('julia-version', version)
@@ -99,21 +56,14 @@ async function run() {
if (!juliaPath) { if (!juliaPath) {
core.debug(`could not find Julia ${arch}/${version} in cache`) core.debug(`could not find Julia ${arch}/${version} in cache`)
const juliaInstallationPath = await installer.installJulia(versionInfo, version, arch)
// https://github.com/julia-actions/setup-julia/pull/196 // Add it to cache
// we want julia to be installed with unmodified file mtimes juliaPath = await tc.cacheDir(juliaInstallationPath, 'julia', version, arch)
// but `tc.cacheDir` uses `cp` internally which destroys mtime
// and `tc` provides no API to get the tool directory alone
// so hack it by installing a empty directory then use the path it returns
// and extract the archives directly to that location
const emptyDir = fs.mkdtempSync('empty')
juliaPath = await tc.cacheDir(emptyDir, 'julia', version, arch)
await installer.installJulia(juliaPath, versionInfo, version, arch)
core.debug(`added Julia to cache: ${juliaPath}`) core.debug(`added Julia to cache: ${juliaPath}`)
// Remove empty dir // Remove temporary dir
fs.rmdirSync(emptyDir) fs.rmdirSync(juliaInstallationPath, {recursive: true})
} else { } else {
core.debug(`using cached version of Julia: ${juliaPath}`) core.debug(`using cached version of Julia: ${juliaPath}`)
} }
@@ -128,7 +78,7 @@ async function run() {
const showVersionInfoInput = core.getInput('show-versioninfo') const showVersionInfoInput = core.getInput('show-versioninfo')
await installer.showVersionInfo(showVersionInfoInput, version) await installer.showVersionInfo(showVersionInfoInput, version)
} catch (error) { } catch (error) {
core.setFailed((error as Error).message) core.setFailed(error.message)
} }
} }