Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ jobs:
- run: |
julia --project=docs -e '
using Pkg
Pkg.develop(PackageSpec(path=pwd()))
Pkg.instantiate()'
- run: |
julia --project=docs -e '
Expand Down
32 changes: 28 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,23 @@ jobs:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

python:
name: Test Python (${{ matrix.pyversion }}, ${{ matrix.os }})
name: Test Python (${{ matrix.pyversion }}, ${{ matrix.os }}, ${{ matrix.juliaexe }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
pyversion: ["3", "3.9"]
juliaexe: ["@JuliaPkg"]
include:
- arch: x64
os: ubuntu-latest
pyversion: 3
juliaexe: julia
env:
MANUAL_TEST_PROJECT: /tmp/juliacall-test-project
PYTHON_JULIACALL_THREADS: '2'
PYTHON_JULIACALL_HANDLE_SIGNALS: 'yes'

steps:
- uses: actions/checkout@v5
Expand All @@ -82,10 +92,18 @@ jobs:
python-version: ${{ matrix.pyversion }}

- name: Set up Julia
id: setup_julia
if: ${{ matrix.juliaexe == 'julia' }}
uses: julia-actions/setup-julia@v2
with:
version: '1'

- name: Set up test Julia project
if: ${{ matrix.juliaexe == 'julia' }}
run: |
mkdir ${{ env.MANUAL_TEST_PROJECT }}
julia --project=${{ env.MANUAL_TEST_PROJECT }} -e 'import Pkg; Pkg.develop(path="."); Pkg.instantiate()'

- name: Install dependencies
run: |
cp pysrc/juliacall/juliapkg-dev.json pysrc/juliacall/juliapkg.json
Expand All @@ -98,12 +116,18 @@ jobs:
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
uv run flake8 ./pysrc ./pytest --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

- name: Run tests
- name: Run tests (with JuliaPkg)
if: ${{ matrix.juliaexe == '@JuliaPkg' }}
run: |
uv run pytest -s --nbval --cov=pysrc ./pytest/

- name: Run tests (without JuliaPkg)
if: ${{ matrix.juliaexe == 'julia' }}
run: |
uv run pytest -s --nbval --cov=pysrc ./pytest/
env:
PYTHON_JULIACALL_THREADS: '2'
PYTHON_JULIACALL_HANDLE_SIGNALS: 'yes'
PYTHON_JULIACALL_EXE: "${{ steps.setup_julia.outputs.julia-bindir }}/julia"
PYTHON_JULIACALL_PROJECT: ${{ env.MANUAL_TEST_PROJECT }}

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
Expand Down
3 changes: 3 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d"

[compat]
Documenter = "1"

[sources]
PythonCall = {path = ".."}
14 changes: 13 additions & 1 deletion docs/src/juliacall.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ What to read next:

## [Managing Julia dependencies](@id julia-deps)

JuliaCall manages its Julia dependencies using [JuliaPkg](https://github.com/JuliaPy/PyJuliaPkg).
By default JuliaCall manages its Julia dependencies using
[JuliaPkg](https://github.com/JuliaPy/PyJuliaPkg).

It will automatically download a suitable version of Julia if required.

Expand All @@ -100,6 +101,15 @@ Alternatively you can use `add`, `rm`, etc. from JuliaPkg to edit this file.

See [JuliaPkg](https://github.com/JuliaPy/PyJuliaPkg) for more details.

### Using existing environments

It's possible to override the defaults and disable JuliaPkg entirely by setting
the `PYTHON_JULIACALL_EXE` and `PYTHON_JULIACALL_PROJECT` options (both must be
set together). This is particularly useful when using shared environments on HPC
systems that may be readonly. Note that the project set in
`PYTHON_JULIACALL_PROJECT` *must* already have PythonCall.jl installed and it
*must* match the JuliaCall version, otherwise loading Julia will fail.

## [Configuration](@id julia-config)

Some features of the Julia process, such as the optimization level or number of threads, may
Expand All @@ -125,6 +135,8 @@ be configured in two ways:
| `-X juliacall-warn-overwrite=<yes\|no>` | `PYTHON_JULIACALL_WARN_OVERWRITE=<yes\|no>` | Enable or disable method overwrite warnings. |
| `-X juliacall-autoload-ipython-extension=<yes\|no>` | `PYTHON_JULIACALL_AUTOLOAD_IPYTHON_EXTENSION=<yes\|no>` | Enable or disable IPython extension autoloading. |
| `-X juliacall-heap-size-hint=<N>` | `PYTHON_JULIACALL_HEAP_SIZE_HINT=<N>` | Hint for initial heap size in bytes. |
| `-X juliacall-exepath=<file>` | `PYTHON_JULIACALL_EXEPATH=<file>` | Path to Julia binary to use (overrides JuliaPkg). |
| `-X juliacall-project=<dir>` | `PYTHON_JULIACALL_PROJECT=<dir>` | Path to the Julia project to use (overrides JuliaPkg). |

## [Multi-threading](@id py-multi-threading)

Expand Down
1 change: 1 addition & 0 deletions docs/src/releasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased
* Bug fixes.
* Internal: switch from Requires.jl to package extensions.
* Added support for specifying the Julia binary and project to override JuliaPkg.

## 0.9.27 (2025-08-19)
* Internal: Use heap-allocated types (PyType_FromSpec) to improve ABI compatibility.
Expand Down
27 changes: 19 additions & 8 deletions pysrc/juliacall/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def int_option(name, *, accept_auto=False, **kw):
raise ValueError(f'{s}: expecting an int'+(' or auto' if accept_auto else ""))

def args_from_config(config):
argv = [config['exepath']]
argv = [config['exe']]
for opt, val in config.items():
if opt.startswith('opt_'):
if val is None:
Expand Down Expand Up @@ -147,21 +147,32 @@ def args_from_config(config):
CONFIG['opt_handle_signals'] = choice('handle_signals', ['yes', 'no'])[0]
CONFIG['opt_startup_file'] = choice('startup_file', ['yes', 'no'])[0]
CONFIG['opt_heap_size_hint'] = option('heap_size_hint')[0]
CONFIG['project'] = path_option('project', check_exists=True)[0]
CONFIG['exe'] = path_option('exe', check_exists=True)[0]

# Stop if we already initialised
if CONFIG['inited']:
return

# we don't import this at the top level because it is not required when juliacall is
# loaded by PythonCall and won't be available
import juliapkg
have_exe = CONFIG['exe'] is not None
have_project = CONFIG['project'] is not None
if not have_exe and not have_project:
# we don't import this at the top level because it is not required when
# juliacall is loaded by PythonCall and won't be available, or if both
# `exe` and `project` are set by the user.
import juliapkg

# Find the Julia executable and project
CONFIG['exepath'] = exepath = juliapkg.executable()
CONFIG['project'] = project = juliapkg.project()
# Find the Julia executable and project
CONFIG['exe'] = juliapkg.executable()
CONFIG['project'] = juliapkg.project()
elif (not have_exe and have_project) or (have_exe and not have_project):
raise Exception("Both PYTHON_JULIACALL_PROJECT and PYTHON_JULIACALL_EXE must be set together, not only one of them.")

exe = CONFIG['exe']
project = CONFIG['project']

# Find the Julia library
cmd = [exepath, '--project='+project, '--startup-file=no', '-O0', '--compile=min',
cmd = [exe, '--project='+project, '--startup-file=no', '-O0', '--compile=min',
'-e', 'import Libdl; print(abspath(Libdl.dlpath("libjulia")), "\\0", Sys.BINDIR)']
libpath, default_bindir = subprocess.run(cmd, check=True, capture_output=True, encoding='utf8').stdout.split('\0')
assert os.path.exists(libpath)
Expand Down
Loading