From 918ed1d724c5675e9d3a9e106ee8ccd33dd97ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 4 Jun 2026 11:24:35 +0100 Subject: [PATCH 1/2] Automatic install of pensdp --- deps/build.jl | 162 ++++++++++++++++++++++++++++++++++++++++++++ src/MOI_wrapper.jl | 97 +++++++++++++++++--------- src/Penopt.jl | 145 ++++++++++++++++++++++++++++++++++++++- test/MOI_wrapper.jl | 18 ++++- test/Project.toml | 4 ++ test/runtests.jl | 7 +- test/sdp.jl | 99 +++++++++++++++++++++++++++ 7 files changed, 492 insertions(+), 40 deletions(-) create mode 100644 deps/build.jl create mode 100644 test/Project.toml create mode 100644 test/sdp.jl diff --git a/deps/build.jl b/deps/build.jl new file mode 100644 index 0000000..dc406ad --- /dev/null +++ b/deps/build.jl @@ -0,0 +1,162 @@ +# Copyright (c) 2019: Benoît Legat and contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +# Automatic installation of the PENSDP library from +# https://github.com/kocvara/pensdp +# +# PENSDP is the free SDP-only variant of the PENOPT family of solvers. The +# BMI-capable PENBMI is a commercial product; if the user has a working +# `libpenbmi` shared library, its path can be provided via the +# `PENOPT_LIBPENBMI` environment variable and it will be used in addition +# to the auto-installed PENSDP library. + +using Libdl + +const PENSDP_VERSION = "2.2" +const PENSDP_REPO = "https://github.com/kocvara/pensdp" +const DEPS_DIR = @__DIR__ +const USR_DIR = joinpath(DEPS_DIR, "usr") +const LIB_DIR = joinpath(USR_DIR, "lib") +const DOWNLOAD_DIR = joinpath(DEPS_DIR, "downloads") + +const ARCHIVES = Dict( + (:linux, :x86_64) => ("pensdp22_LNX64.tar.gz", :tar), + (:apple, :x86_64) => ("pensdp22_macos_intel64.zip", :zip), + (:windows, :x86_64) => ("pensdp22_Win64.zip", :zip), +) + +function _platform() + if Sys.islinux() + return (:linux, Sys.ARCH) + elseif Sys.isapple() + return (:apple, Sys.ARCH) + elseif Sys.iswindows() + return (:windows, Sys.ARCH) + else + error("Unsupported platform: $(Sys.KERNEL)") + end +end + +function _archive_url() + key = _platform() + if !haskey(ARCHIVES, key) + error( + "No pre-built PENSDP archive is published for $(key). " * + "See $(PENSDP_REPO).", + ) + end + name, kind = ARCHIVES[key] + return "$(PENSDP_REPO)/raw/main/bin/$(name)", name, kind +end + +function _download_and_extract() + mkpath(DOWNLOAD_DIR) + url, name, kind = _archive_url() + archive = joinpath(DOWNLOAD_DIR, name) + if !isfile(archive) + @info "Downloading $(url)" + download(url, archive) + end + extract_dir = joinpath(DOWNLOAD_DIR, "Pensdp$(PENSDP_VERSION)") + rm(extract_dir; force = true, recursive = true) + @info "Extracting $(archive)" + if kind == :tar + run(`tar -xzf $(archive) -C $(DOWNLOAD_DIR)`) + else + run(`unzip -q -o $(archive) -d $(DOWNLOAD_DIR)`) + end + return extract_dir +end + +# Build a shared library that exports the `pensdp` symbol so that it can be +# loaded by Julia via `Libdl.dlopen` and called with `ccall`. +function _build_shared_library(extract_dir) + mkpath(LIB_DIR) + lib_src = joinpath(extract_dir, "lib") + libpensdp_a = joinpath(lib_src, "libpensdp.a") + isfile(libpensdp_a) || error("$(libpensdp_a) not found") + julia_libdir = joinpath(Sys.BINDIR, Base.LIBDIR, "julia") + os, _ = _platform() + if os == :linux + output = joinpath(LIB_DIR, "libpensdp.so") + libgoto = joinpath(lib_src, "libgoto2.a") + isfile(libgoto) || error("$(libgoto) not found") + cmd = `gcc -shared -fPIC -o $(output) + -L$(julia_libdir) -Wl,-rpath,$(julia_libdir) + -Wl,--whole-archive $(libpensdp_a) -Wl,--no-whole-archive + $(libgoto) + -lgfortran -lpthread -ldl -lm` + @info "Linking shared library" cmd + run(cmd) + return output + elseif os == :apple + output = joinpath(LIB_DIR, "libpensdp.dylib") + libgfortran_mac = joinpath(lib_src, "libgfortran_mac.a") + cmd = `gcc -dynamiclib -o $(output) + -Wl,-force_load,$(libpensdp_a) + $(libgfortran_mac) + -framework Accelerate + -lpthread` + @info "Linking shared library" cmd + run(cmd) + return output + else + error( + "Automatic build is currently implemented for Linux and macOS. " * + "On Windows please set `PENOPT_LIBPENBMI` (or build " * + "`libpensdp.dll` manually) and re-run `Pkg.build(\"Penopt\")`.", + ) + end +end + +function _write_deps(libpensdp_path) + libpenbmi_path = get(ENV, "PENOPT_LIBPENBMI", "") + open(joinpath(DEPS_DIR, "deps.jl"), "w") do io + println(io, "# Auto-generated by deps/build.jl - do not edit.") + println(io, "import Libdl") + println(io) + println(io, "const libpensdp = ", repr(libpensdp_path)) + # When `libpenbmi` is not available, the constant is left as an empty + # string. `has_penbmi()` guards every ccall in `Penopt.jl`, so the + # `ccall((:penbmi, libpenbmi), ...)` site is never reached. + println(io, "const libpenbmi = ", repr(libpenbmi_path)) + println(io) + println(io, """ + function check_deps() + if !isfile(libpensdp) + error( + \"\$(libpensdp) does not exist, please re-run \" * + \"Pkg.build(\\\"Penopt\\\") and restart Julia.\", + ) + end + if Libdl.dlopen_e(libpensdp) == C_NULL + error( + \"\$(libpensdp) cannot be opened, please re-run \" * + \"Pkg.build(\\\"Penopt\\\") and restart Julia.\", + ) + end + if !isempty(libpenbmi) + if !isfile(libpenbmi) + error(\"\$(libpenbmi) does not exist.\") + end + if Libdl.dlopen_e(libpenbmi) == C_NULL + error(\"\$(libpenbmi) cannot be opened.\") + end + end + return + end""") + end + return +end + +function main() + extract_dir = _download_and_extract() + libpensdp_path = _build_shared_library(extract_dir) + _write_deps(libpensdp_path) + @info "Penopt: PENSDP installed at $(libpensdp_path)" + return +end + +main() diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index cbb8e87..6cdfa40 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -109,7 +109,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer end end -MOI.get(::Optimizer, ::MOI.SolverName) = "Penbmi" +MOI.get(::Optimizer, ::MOI.SolverName) = has_penbmi() ? "Penbmi" : "Pensdp" function MOI.supports(optimizer::Optimizer, param::MOI.RawOptimizerAttribute) return param.name in IOPTIONS || param.name in FOPTIONS @@ -482,6 +482,9 @@ function MOI.add_constraint( return MOI.ConstraintIndex{typeof(func),typeof(set)}(ci.value) end +_is_sdp(optimizer::Optimizer) = + isempty(optimizer.q_val) && isempty(optimizer.ki_val) + function MOI.optimize!(optimizer::Optimizer) ioptions = optimizer.ioptions if optimizer.silent @@ -490,38 +493,66 @@ function MOI.optimize!(optimizer::Optimizer) ioptions[11] = 0 # DIMACS : no end optimizer.x = copy(optimizer.x0) - return optimizer.fx, - _, - optimizer.uoutput, - optimizer.iresults, - optimizer.fresults, - optimizer.info = penbmi( - optimizer.msizes, - optimizer.x, - optimizer.fobj, - optimizer.q_col, - optimizer.q_row, - optimizer.q_val, - optimizer.ci, - optimizer.bi_dim, - optimizer.bi_idx, - optimizer.bi_val, - optimizer.ai_dim, - optimizer.ai_idx, - optimizer.ai_nzs, - optimizer.ai_val, - optimizer.ai_col, - optimizer.ai_row, - optimizer.ki_dim, - optimizer.ki_idx, - optimizer.kj_idx, - optimizer.ki_nzs, - optimizer.ki_val, - optimizer.ki_col, - optimizer.ki_row, - ioptions, - optimizer.foptions, - ) + # PENSDP (auto-installed) handles SDP. Fall back to PENBMI when the + # objective has a quadratic part or when there are BMI (`K`) terms. + if _is_sdp(optimizer) && !has_penbmi() + optimizer.fx, + _, + optimizer.uoutput, + optimizer.iresults, + optimizer.fresults, + optimizer.info = pensdp( + optimizer.msizes, + optimizer.x, + optimizer.fobj, + optimizer.ci, + optimizer.bi_dim, + optimizer.bi_idx, + optimizer.bi_val, + optimizer.ai_dim, + optimizer.ai_idx, + optimizer.ai_nzs, + optimizer.ai_val, + optimizer.ai_col, + optimizer.ai_row, + ioptions, + optimizer.foptions, + ) + else + optimizer.fx, + _, + optimizer.uoutput, + optimizer.iresults, + optimizer.fresults, + optimizer.info = penbmi( + optimizer.msizes, + optimizer.x, + optimizer.fobj, + optimizer.q_col, + optimizer.q_row, + optimizer.q_val, + optimizer.ci, + optimizer.bi_dim, + optimizer.bi_idx, + optimizer.bi_val, + optimizer.ai_dim, + optimizer.ai_idx, + optimizer.ai_nzs, + optimizer.ai_val, + optimizer.ai_col, + optimizer.ai_row, + optimizer.ki_dim, + optimizer.ki_idx, + optimizer.kj_idx, + optimizer.ki_nzs, + optimizer.ki_val, + optimizer.ki_col, + optimizer.ki_row, + ioptions, + optimizer.foptions, + ) + end + return end function MOI.get(optimizer::Optimizer, ::MOI.SolveTimeSec) diff --git a/src/Penopt.jl b/src/Penopt.jl index 5d12f9d..43ad64c 100644 --- a/src/Penopt.jl +++ b/src/Penopt.jl @@ -15,6 +15,8 @@ else ) end +has_penbmi() = !isempty(libpenbmi) + const DEFAULT_IOPTIONS = Cint[1, 50, 100, 2, 0, 0, 0, 0, 0, 0, 1, 0] const DEFAULT_FOPTIONS = Cdouble[ 1.0, @@ -50,11 +52,11 @@ function penbmi( fobj::Vector{Cdouble}, q_col::Vector{Cint}, q_row::Vector{Cint}, - q_val::Vector{Cdouble}, # Mismatch with doc which starts with q_val, assuming the doc is false - ci::Vector{Cdouble}, # c vector + q_val::Vector{Cdouble}, + ci::Vector{Cdouble}, bi_dim::Vector{Cint}, bi_idx::Vector{Cint}, - bi_val::Vector{Cdouble}, # b vectors + bi_val::Vector{Cdouble}, ai_dim::Vector{Cint}, ai_idx::Vector{Cint}, ai_nzs::Vector{Cint}, @@ -109,6 +111,13 @@ function penbmi( @assert length(ki_row) == sum(ki_nzs) @assert length(ioptions) == 12 @assert length(foptions) == 12 + has_penbmi() || error( + "PENBMI is not available. PENBMI is a commercial product; set the " * + "`PENOPT_LIBPENBMI` environment variable to the path of `libpenbmi` " * + "and re-run `Pkg.build(\"Penopt\")`. PENSDP (auto-installed) only " * + "supports linear objectives and linear matrix inequalities; use " * + "`Penopt.pensdp` for those problems.", + ) fx = Ref{Cdouble}(zero(Cdouble)) iresults = zeros(Cint, 4) fresults = zeros(Cdouble, 5) @@ -195,6 +204,136 @@ function penbmi( return fx[], x0, uoutput, iresults, fresults, info[] end +""" + pensdp(msizes, x0, fobj, ci, + bi_dim, bi_idx, bi_val, + ai_dim, ai_idx, ai_nzs, ai_val, ai_col, ai_row, + ioptions, foptions) + +Solve a semidefinite program of the form + +``` +min f'x +bi'x ≤ ci i = 1, ..., constr +A0i + sum_k x_k * Ai_k ⪯ 0 i = 1, ..., mconstr +``` + +This is the linear-objective, linear-matrix-inequality subset of [`penbmi`](@ref). +""" +function pensdp( + msizes::Vector{Cint}, + x0::Vector{Cdouble}, + fobj::Vector{Cdouble}, + ci::Vector{Cdouble}, + bi_dim::Vector{Cint}, + bi_idx::Vector{Cint}, + bi_val::Vector{Cdouble}, + ai_dim::Vector{Cint}, + ai_idx::Vector{Cint}, + ai_nzs::Vector{Cint}, + ai_val::Vector{Cdouble}, + ai_col::Vector{Cint}, + ai_row::Vector{Cint}, + ioptions = Cint[1, 50, 100, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1], + foptions = Cdouble[ + 1.0, + 0.7, + 0.1, + 1e-7, + 1e-6, + 1e-14, + 1e-2, + 1e-1, + 0.0, + 1.0, + 1.0e-6, + 5.0e-2, + ], +) + mconstr = length(msizes) + vars = length(x0) + @assert vars == length(fobj) + @assert mconstr == length(ai_dim) + constr = length(ci) + @assert length(bi_dim) == constr + @assert length(bi_idx) == sum(bi_dim) + @assert length(bi_val) == sum(bi_dim) + @assert length(ai_idx) == sum(ai_dim) + @assert length(ai_nzs) == sum(ai_dim) + @assert length(ai_val) == sum(ai_nzs) + @assert length(ai_col) == sum(ai_nzs) + @assert length(ai_row) == sum(ai_nzs) + # PENSDP exposes 15 integer options; pad the trailing 3 with the defaults + # documented in `Pensdp2.2/c/driver_sdp_c.c` when only the 12 PENBMI-common + # options are provided. + if length(ioptions) == 12 + ioptions = vcat(ioptions, Cint[0, 1, 1]) + end + @assert length(ioptions) == 15 + @assert length(foptions) == 12 + fx = Ref{Cdouble}(zero(Cdouble)) + iresults = zeros(Cint, 4) + fresults = zeros(Cdouble, 5) + info = Ref{Cint}(zero(Cint)) + u0 = C_NULL + uoutput = zeros(Cdouble, constr + sum(_tridim, msizes, init = 0)) + ccall( + (:pensdp, libpensdp), + Cint, + ( + Cint, + Cint, + Cint, + Ptr{Cint}, + Ref{Cdouble}, + Ptr{Cdouble}, + Ptr{Cdouble}, + Ptr{Cdouble}, + Ptr{Cdouble}, + Ptr{Cdouble}, + Ptr{Cint}, + Ptr{Cint}, + Ptr{Cdouble}, + Ptr{Cint}, + Ptr{Cint}, + Ptr{Cint}, + Ptr{Cdouble}, + Ptr{Cint}, + Ptr{Cint}, + Ptr{Cint}, + Ptr{Cdouble}, + Ptr{Cint}, + Ptr{Cdouble}, + Ref{Cint}, + ), + vars, + constr, + mconstr, + msizes, + fx, + x0, + u0, + uoutput, + fobj, + ci, + bi_dim, + bi_idx, + bi_val, + ai_dim, + ai_idx, + ai_nzs, + ai_val, + ai_col, + ai_row, + ioptions, + foptions, + iresults, + fresults, + info, + ) + return fx[], x0, uoutput, iresults, fresults, info[] +end + include("MOI_wrapper.jl") end # module diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 4ff9918..33a7cb6 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -23,7 +23,8 @@ function runtests() end function test_solver_name() - @test MOI.get(Penopt.Optimizer(), MOI.SolverName()) == "Penbmi" + expected = Penopt.has_penbmi() ? "Penbmi" : "Pensdp" + @test MOI.get(Penopt.Optimizer(), MOI.SolverName()) == expected end function test_supports_default_copy_to() @@ -48,6 +49,17 @@ function test_runtests() MOI.set(model, MOI.Silent(), true) MOI.set(model, MOI.RawOptimizerAttribute("PBM_EPS"), 1e-2) MOI.set(model, MOI.RawOptimizerAttribute("P0"), 1e-2) + # When libpenbmi is not available, the auto-installed PENSDP cannot solve + # problems with a quadratic objective. + bmi_only = Penopt.has_penbmi() ? String[] : String[ + "test_objective_qp_ObjectiveFunction_edge_cases", + "test_objective_qp_ObjectiveFunction_zero_ofdiag", + "test_quadratic_duplicate_terms", + "test_quadratic_integration", + "test_quadratic_nonhomogeneous", + # PENSDP reports OTHER_ERROR for this edge case; PENBMI handles it. + "test_conic_empty_matrix", + ] MOI.Test.runtests( model, MOI.Test.Config( @@ -64,7 +76,7 @@ function test_runtests() MOI.DualObjectiveValue, ], ), - exclude = String[ + exclude = vcat(bmi_only, String[ # Unable to bridge RotatedSecondOrderCone to PSD because the dimension is too small: got 2, expected >= 3. "test_conic_SecondOrderCone_INFEASIBLE", "test_constraint_PrimalStart_DualStart_SecondOrderCone", @@ -109,7 +121,7 @@ function test_runtests() "test_objective_ObjectiveFunction_constant", "test_objective_ObjectiveFunction_duplicate_terms", "test_solve_result_index", - ], + ]), ) return end diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 0000000..6482d83 --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,4 @@ +[deps] +MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +Penopt = "fb8f6f4e-b8f1-11e9-2b95-bdba3fec731e" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/runtests.jl b/test/runtests.jl index 8d21110..12961bc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,11 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. +using Penopt + include("utilities.jl") -include("bmi.jl") +include("sdp.jl") +if Penopt.has_penbmi() + include("bmi.jl") +end include("MOI_wrapper.jl") diff --git a/test/sdp.jl b/test/sdp.jl new file mode 100644 index 0000000..1131635 --- /dev/null +++ b/test/sdp.jl @@ -0,0 +1,99 @@ +# Copyright (c) 2019: Benoît Legat and contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestSDP + +# Example taken from `Pensdp2.2/c/driver_sdp_c.c` (the SDP part of Example 3 +# in http://www.penopt.com/doc/penbmi2_1.pdf). +using Test +using Penopt + +import MathOptInterface +const MOI = MathOptInterface + +function test_driver_sdp_c() + msizes = Cint[3] + x0 = zeros(Cdouble, 3) + fobj = Cdouble[0.0, 0.0, 1.0] + + ci = Cdouble[0.5, 2.0, 3.0, 7.0] + bi_dim = ones(Cint, 4) + bi_idx = Cint[0, 0, 1, 1] + bi_val = Cdouble[-1.0, 1.0, -1.0, 1.0] + + ai_dim = Cint[4] + ai_idx = Cint[0, 1, 2, 3] + ai_nzs = Cint[4, 4, 5, 3] + ai_col = Cint[0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 2, 0, 1, 2] + ai_row = Cint[0, 0, 0, 1, 0, 0, 1, 2, 0, 0, 0, 1, 1, 0, 1, 2] + ai_val = Cdouble[ + -10.0, -0.5, -2.0, 4.5, + 9.0, 0.5, -3.0, -1.0, + -1.8, -0.1, -0.4, 1.2, -1.0, + -1.0, -1.0, -1.0, + ] + + ioptions = Cint[1, 50, 100, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1] + foptions = Cdouble[1.0, 0.7, 0.1, 1e-7, 1e-6, 1e-14, 1e-2, 1e-1, 0.0, 1.0, 1.0e-6, 5.0e-2] + + x = copy(x0) + fx, xx, uoutput, iresults, fresults, info = Penopt.pensdp( + msizes, x, fobj, + ci, + bi_dim, bi_idx, bi_val, + ai_dim, ai_idx, ai_nzs, ai_val, ai_col, ai_row, + ioptions, foptions, + ) + + @test xx === x + @test info == 0 + @test length(iresults) == 4 + @test iresults isa Vector{Cint} + @test length(fresults) == 5 + @test isfinite(fx) +end + +function test_moi_sdp() + optimizer = Penopt.Optimizer() + MOI.set(optimizer, MOI.RawOptimizerAttribute("OUTPUT"), 0) + MOI.set(optimizer, MOI.RawOptimizerAttribute("DIMACS"), 0) + x = MOI.add_variables(optimizer, 3) + obj = 1.0x[3] + MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.set(optimizer, MOI.ObjectiveFunction{typeof(obj)}(), obj) + MOI.add_constraint(optimizer, -1.0x[1], MOI.LessThan(0.5)) + MOI.add_constraint(optimizer, 1.0x[1], MOI.LessThan(2.0)) + MOI.add_constraint(optimizer, -1.0x[2], MOI.LessThan(3.0)) + MOI.add_constraint(optimizer, 1.0x[2], MOI.LessThan(7.0)) + func = MOI.Utilities.vectorize(MOI.ScalarAffineFunction{Cdouble}[ + 10.0 - 9.0x[1] + 1.8x[2] + 1.0x[3], + 0.5 - 0.5x[1] + 0.1x[2], + -4.5 - 1.2x[2] + 1.0x[3], + 2.0 + 0.4x[2], + 3.0x[1] + 1.0x[2], + 1.0x[1] + 1.0x[3], + ]) + MOI.add_constraint(optimizer, func, MOI.PositiveSemidefiniteConeTriangle(3)) + + MOI.optimize!(optimizer) + + @test MOI.get(optimizer, MOI.RawStatusString()) == "No errors." + @test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.LOCALLY_SOLVED + @test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT +end + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end +end + +end # module TestSDP + +TestSDP.runtests() From 6ef6ceb0ef939f207385906032e58c78e0b098f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 4 Jun 2026 11:25:50 +0100 Subject: [PATCH 2/2] Add ci --- .github/dependabot.yml | 11 +++++++++++ .github/workflows/ci.yml | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..147c241 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "julia" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f7ee3f7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize, reopened] +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - version: '1.10' + os: ubuntu-latest + arch: x64 + - version: '1' + os: ubuntu-latest + arch: x64 + steps: + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v3 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v3 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + with: + depwarn: error + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v6 + with: + files: lcov.info + token: ${{ secrets.CODECOV_TOKEN }}