Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
11 changes: 11 additions & 0 deletions .github/workflows/run-tests-and-build-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ jobs:
strict: false
test-results-junit: test-results/results.xml
code-coverage-cobertura: test-results/coverage.xml

- name: Run tests (without coverage)
if: ${{ steps.coverage-support.outputs.supported == 'false' }}
uses: matlab-actions/run-tests@v3
env:
JULIA_VERSIONS: ${{ inputs.julia-version }}
with:
source-folder: 'src/matlab'
select-by-folder: 'test'
strict: false
test-results-junit: test-results/results.xml

- name: Upload MEX binary
if: ${{ inputs.mex-recompile }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ CMakeLists.txt
*.mexa64
*.asv
Manifest.toml
@matfrostjulia/*
6 changes: 3 additions & 3 deletions Artifacts.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[matfrost-mex]
git-tree-sha1 = "b61d9d5d3450faa12efca586a0605c176fb1fd6f"
git-tree-sha1 = "f2384de32a78c2ddf9f22668f039a56d2754fafd"

[[matfrost-mex.download]]
sha256 = "7ddbd67a7e553c816ed3a6987363ae16e00f4e9483afc002af7bb0343259b50e"
url = "https://github.com/ASML-Labs/MATFrost.jl/releases/download/matfrost-mex-v0.5.0-beta.1/matfrost-mex-v0.5.0-beta.1-win-x64.tar.gz"
sha256 = "bd237ef86b071f2bc78617d64578c75287d70812458b56d58a98e43232ff77e0"
url = "https://github.com/ASML-Labs/MATFrost.jl/releases/download/v0.6.0/matfrost-mex-v0.6.0.tar.gz"
114 changes: 114 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Contributing to MATFrost.jl

Thank you for contributing.

This guide focuses on the developer workflow for **MEX binaries** (`matfrostjuliacall.mex*`), because this is the part that is easiest to get wrong when setting up locally.

---

## 1) Use released MEX binaries (recommended)

From repository root, install MATLAB bindings using the artifact defined in `Artifacts.toml`:

```matlab
% MATLAB (run from repo root)
system('julia --project=. -e "using MATFrost; MATFrost.install()"');
```

This will:
- create/update `@matfrostjulia` in the current folder
- copy MATLAB wrapper files from `src/matlab/@matfrostjulia`
- copy released MEX binaries from Julia artifact `matfrost-mex` into `@matfrostjulia/private`

---

## 2) Verify binaries are present

After install, verify these files exist in `@matfrostjulia/private`:
- `matfrostjuliacall.mexw64` (Windows)
- `matfrostjuliacall.mexa64` (Linux, when included in release bundle)

If missing, see Troubleshooting below.

---

## 3) Updating MATFrost to a new released MEX bundle

When a new MEX release is published (for example `v0.6.0`), update artifact metadata so `MATFrost.install()` pulls the new bundle.

### Step A: Download release tarball
Download release asset:
- `matfrost-mex-v<version>.tar.gz`

### Step B: Rebind artifact metadata
Run from repo root:

```powershell
julia --project=. src/matfrostjuliacall/integrate_released_mexbinaries.jl <version> <path-to-tar.gz>
```

Example:

```powershell
julia --project=. src/matfrostjuliacall/integrate_released_mexbinaries.jl 0.6.0 C:\temp\matfrost-mex-v0.6.0.tar.gz
```

This script updates `Artifacts.toml` with:
- `git-tree-sha1`
- download URL (`.../releases/download/v<version>/matfrost-mex-v<version>.tar.gz`)
- `sha256`

### Step C: Reinstall bindings

```matlab
system('julia --project=. -e "using MATFrost; MATFrost.install()"');
```

---

## 4) Build MEX locally (only if needed)

Use local build only when developing C++ MEX changes.

Entry point:
- `src/matfrostjuliacall/matfrostmake.m`

Windows notes:
- Install MinGW-w64 compatible with MATLAB mex.
- Set env var `MW_MINGW64_LOC`.
- `matfrostmake` writes output into `src/matlab/@matfrostjulia/private`.

For most contributors, **prefer released binaries** over local build.

---

## 5) Common pitfalls

### Wrong MATFrost version in bootstrap
If MATLAB starts but server exits immediately with version mismatch, verify:
- `src/matlab/@matfrostjulia/bootstrap.jl` has the correct `MATFROST_MATLAB_VERSION`
- installed copy under `@matfrostjulia/bootstrap.jl` matches

### Stale binaries still loaded
Re-run install from repo root:

```matlab
system('julia --project=. -e "using MATFrost; MATFrost.install()"');
```

This refreshes `@matfrostjulia/private` from current artifact metadata.

### Active project conflict (`Pkg.add("MATFrost")`)
Inside a local checkout, do **not** run `Pkg.add("MATFrost")` in the MATFrost project itself.
Use `using MATFrost; MATFrost.install()` with `--project=.` instead.

---

## 6) Related files

- `Artifacts.toml`
- `src/install.jl`
- `src/matlab/@matfrostjulia/bootstrap.jl`
- `src/matfrostjuliacall/integrate_released_mexbinaries.jl`
- `src/matfrostjuliacall/matfrostmake.m`
- `.github/CONTRIBUTING.md` (CI/test policy)
36 changes: 30 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ Characteristics:
# Quick start 🚀
```matlab
% MATLAB
system('julia -e "import Pkg ; Pkg.add(ARGS[1]) ; using MATFrost ; MATFrost.install()" "MATFrost"');
% Install MATLAB bindings. This will install @matfrostjulia inside current working directory.
system('julia --project=. -e "using MATFrost ; MATFrost.install()"');
% Install MATLAB bindings from a local MATFrost checkout.
% This will install @matfrostjulia inside the current working directory.

jl = matfrostjulia();
% Spawn a matfrostjulia server running JULIA
Expand Down Expand Up @@ -105,21 +106,44 @@ You can then use MATFrost from MATLAB to create and add `Point` objects, specify
```matlab
% MATLAB
% Create two Julia Point objects
p1 = tc.mjl.MATFrostTest.Point(int64(1), int64(2),signature=["Int64","Int64"]);
p2 = tc.mjl.MATFrostTest.Point(int64(3), int64(4),signature=["Int64","Int64"]);
p1 = jl.MATFrostTest.Point(int64(1), int64(2),signature=["Int64","Int64"]);
p2 = jl.MATFrostTest.Point(int64(3), int64(4),signature=["Int64","Int64"]);

% Call the overloaded Base.+ method for Point
res = tc.mjl.Base.('+')(p1, p2, signature=["MATFrostTest.Point", "MATFrostTest.Point"]); % returns Point(4, 6)
res = jl.Base.('+')(p1, p2, signature=["MATFrostTest.Point", "MATFrostTest.Point"]); % returns Point(4, 6)
```

Here, `signature=["MyGeometry.Point", "MyGeometry.Point"]` ensures the correct method for adding two `Point` objects is called.
Here, `signature=["MATFrostTest.Point", "MATFrostTest.Point"]` ensures the correct method for adding two `Point` objects is called.

**Notes:**
- Use a string for a single type, or a cell/string array for multiple types.
- The types in `signature` must match the Julia method’s argument types exactly.

This feature allows you to disambiguate overloaded Julia functions directly from MATLAB.

## Keyword arguments (`kwargs`)

MATFrost supports calling Julia keyword arguments from MATLAB.

Preferred (Julia-like) syntax:

```matlab
% MATLAB
res = jl.MATFrostTest.affine_with_kwargs(2.0, scale=3.0, bias=1.0, signature="Float64");
```

Alternative explicit object syntax:

```matlab
% MATLAB
kw = jl.kwargs("scale", 3.0, "bias", 1.0);
res = jl.MATFrostTest.affine_with_kwargs(2.0, kw, signature="Float64");
```

Notes:
- Positional arguments must come before keyword arguments.
- If a call is ambiguous because positional values look like keyword names, use `jl.kwargs(...)` explicitly.

## Type mapping

### Scalars and Arrays conversions
Expand Down
39 changes: 39 additions & 0 deletions src/matlab/@kwargs/kwargs.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
classdef kwargs

properties (SetAccess=immutable)
Data struct
end

methods
function obj = kwargs(varargin)
if nargin == 0
obj.Data = struct();
return
end

if mod(nargin, 2) ~= 0
error("Kwargs:InvalidInput", "Expected name/value pairs.");
end

data = struct();
for k = 1:2:nargin
key = varargin{k};
if isstring(key) && isscalar(key)
name = char(key);
elseif ischar(key)
name = key;
else
error("Kwargs:InvalidName", "Keyword name at position %d must be a string scalar or char vector.", k);
end

if ~isvarname(name)
error("Kwargs:InvalidName", "Invalid Julia keyword argument name: %s", name);
end

data.(name) = varargin{k+1};
end

obj.Data = data;
end
end
end
Loading
Loading