diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 897bc794..7e553365 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,14 +10,20 @@ permissions: contents: read jobs: test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + name: Julia ${{ matrix.version }}-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.XPRESS_JLL_VERSION }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: version: ['1.6', '1'] # Test against LTS and current minor release - os: [ubuntu-latest, macOS-latest] + os: [ubuntu-latest, macOS-latest, windows-latest] arch: [x64] + XPRESS_JLL_VERSION: ['8.13.4', '9.3.0'] + include: + - version: '1' + os: macos-14 + arch: aarch64 + XPRESS_JLL_VERSION: '9.3.0' steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v1 @@ -26,7 +32,17 @@ jobs: arch: ${{ matrix.arch }} - uses: julia-actions/cache@v1 - uses: julia-actions/julia-buildpkg@v1 + env: + XPRESS_JL_SKIP_LIB_CHECK: "true" + - shell: julia --project=. --color=yes {0} + run: | + import Pkg + Pkg.add(; name = "Xpress_jll", version = ENV["XPRESS_JLL_VERSION"]) + env: + XPRESS_JLL_VERSION: ${{ matrix.XPRESS_JLL_VERSION }} - uses: julia-actions/julia-runtest@v1 + env: + XPRESS_JL_COMMUNITY_XPAUTH_XPR: ${{ secrets.XPRESS_JL_COMMUNITY_XPAUTH_XPR }} - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v1 with: diff --git a/Project.toml b/Project.toml index 6e22d46f..ca6b7b9a 100644 --- a/Project.toml +++ b/Project.toml @@ -5,19 +5,19 @@ url = "https://github.com/jump-dev/Xpress.jl" version = "0.16.2" [deps] -Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" [compat] -Downloads = "<0.0.1, 1" Libdl = "<0.0.1, 1.6" MathOptInterface = "1" Test = "<0.0.1, 1.6" +Xpress_jll = "=8.13.4, =9.3.0" julia = "1.6" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Xpress_jll = "308bddfa-7f95-4fa6-a557-f2c7addc1869" [targets] -test = ["Test"] +test = ["Test", "Xpress_jll"] diff --git a/README.md b/README.md index be5e14ce..0776441a 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,11 @@ The underlying solver is a closed-source commercial product for which you must First, obtain a license of Xpress and install Xpress solver, following the [instructions on the FICO website](https://www.fico.com/products/fico-xpress-solver). +Ensure that the `XPRESSDIR` license variable is set to the install location by +checking the output of: +```julia +julia> ENV["XPRESSDIR"] +``` Then, install this package using: ```julia @@ -46,12 +51,47 @@ import Pkg Pkg.add("Xpress") ``` +### Skipping installation + By default, building Xpress.jl will fail if the Xpress library is not found. + This may not be desirable in certain cases, for example when part of a package's test suite uses Xpress as an optional test dependency, but Xpress cannot be -installed on a CI server running the test suite. To support this use case, the -`XPRESS_JL_SKIP_LIB_CHECK` environment variable may be set (to any value) to -make Xpress.jl installable (but not usable). +installed on a CI server running the test suite. + +To skip the error, set the `XPRESS_JL_SKIP_LIB_CHECK` environment variable to +`true` to make Xpress.jl installable (but not usable). + +```julia +ENV["XPRESS_JL_SKIP_LIB_CHECK"] = true +import Pkg +Pkg.add("Xpress") +``` + +## Use with Xpress_jll + +Instead of manually installing Xpress, you can use the binaries provided by the +[Xpress_jll.jl](https://github.com/jump-dev/Xpress_jll.jl) package. + +By using Xpress_jll, you agree to certain license conditions. See the +[Xpress_jll.jl README](https://github.com/jump-dev/Xpress_jll.jl/tree/master?tab=readme-ov-file#license) +for more details. + +By default, `Xpress_jll` includes a limited size community license. If you have +purchased a license for FICO Xpress, you should additionally set the +`XPAUTH_PATH` environment variable to point to your license file. + +```julia +import Xpress_jll +# This environment variable must be set _before_ loading Xpress.jl +ENV["XPRESS_JL_LIBRARY"] = Xpress_jll.libxprs +# Optional: point to the directory of your xpauth.xpr license file +ENV["XPAUTH_PATH"] = "/path/to/directory" +using Xpress +``` + +If you plan to use Xpress_jll, `Pkg.add("Xpress")` will fail because it cannot +find a local installation of Xpress. Therefore, you should ## Use with JuMP @@ -102,6 +142,8 @@ current implementation should be considered experimental. - `XPRESS_JL_NO_DEPS_ERROR`: Disable error when do deps.jl file is found. - `XPRESS_JL_NO_AUTO_INIT`: Disable automatic run of `Xpress.initialize()`. Specially useful for explicitly loading the dynamic library. + - `XPRESS_JL_LIBRARY`: Provide a custom path to `libxprs` + - `XPAUTH_PATH`: Provide a custom path to the license file ## C API diff --git a/deps/build.jl b/deps/build.jl index 20128e87..44455184 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -3,7 +3,6 @@ # 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. -import Downloads import Libdl const DEPS_FILE = joinpath(dirname(@__FILE__), "deps.jl") @@ -41,24 +40,6 @@ function local_installation() """) end -function ci_installation() - url = if Sys.islinux() - "https://anaconda.org/fico-xpress/xpress/9.3.0/download/linux-64/xpress-9.3.0-py310ha14b774_0.tar.bz2" - else - @assert Sys.isapple() - "https://anaconda.org/fico-xpress/xpress/9.3.0/download/osx-64/xpress-9.3.0-py310h9b76c6a_0.tar.bz2" - end - Downloads.download(url, "xpress.tar.bz2") - run(`tar -xjf xpress.tar.bz2`) - root = "lib/python3.10/site-packages/xpress" - run(`cp $root/license/community-xpauth.xpr $root/lib/xpauth.xpr`) - if Sys.islinux() - run(`cp $root/lib/libxprs.so.42 $root/lib/libxprs.so`) - end - write_deps_file(joinpath(@__DIR__, root, "lib")) - return -end - if isfile(DEPS_FILE) rm(DEPS_FILE) end @@ -67,8 +48,6 @@ if haskey(ENV, "XPRESS_JL_SKIP_LIB_CHECK") # Skip! elseif get(ENV, "JULIA_REGISTRYCI_AUTOMERGE", "false") == "true" write_deps_file("julia_registryci_automerge") -elseif get(ENV, "CI", "") == "true" - ci_installation() else local_installation() end diff --git a/src/Lib/Lib.jl b/src/Lib/Lib.jl index 046468e5..447f4f06 100644 --- a/src/Lib/Lib.jl +++ b/src/Lib/Lib.jl @@ -6,7 +6,9 @@ module Lib import ..Xpress -const libxprs = Xpress.libxprs +global libxprs = Xpress.libxprs + +set_libxprs(libxprs_) = (global libxprs = libxprs_) include("common.jl") include("xprs.jl") diff --git a/src/Xpress.jl b/src/Xpress.jl index bd962bba..cacafea8 100644 --- a/src/Xpress.jl +++ b/src/Xpress.jl @@ -13,17 +13,14 @@ const depsjl_path = joinpath(@__DIR__, "..", "deps", "deps.jl") if isfile(depsjl_path) include(depsjl_path) -elseif !haskey(ENV, "XPRESS_JL_NO_DEPS_ERROR") - error("XPRESS cannot be loaded. Please run Pkg.build(\"Xpress\").") + global libxprs = joinpath( + xpressdlpath, + string(Sys.iswindows() ? "" : "lib", "xprs", ".", Libdl.dlext), + ) else - const xpressdlpath = "" + global libxprs = "" end -const libxprs = joinpath( - xpressdlpath, - string(Sys.iswindows() ? "" : "lib", "xprs", ".", Libdl.dlext), -) - include("Lib/Lib.jl") include("helper.jl") include("api.jl") @@ -69,6 +66,12 @@ include("MOI/MOI_wrapper.jl") include("MOI/MOI_callbacks.jl") function __init__() + if haskey(ENV, "XPRESS_JL_LIBRARY") + global libxprs = ENV["XPRESS_JL_LIBRARY"] + Lib.set_libxprs(libxprs) + elseif isempty(libxprs) && !haskey(ENV, "XPRESS_JL_NO_DEPS_ERROR") + error("XPRESS cannot be loaded. Please run Pkg.build(\"Xpress\").") + end if !haskey(ENV, "XPRESS_JL_NO_AUTO_INIT") && get(ENV, "JULIA_REGISTRYCI_AUTOMERGE", "false") != "true" initialize() diff --git a/src/license.jl b/src/license.jl index 0d73827d..429b7a05 100644 --- a/src/license.jl +++ b/src/license.jl @@ -21,6 +21,11 @@ function _get_xpauthpath(xpauth_path = "", verbose::Bool = true) # Search in `xpress/lib/../bin/xpauth.xpr`. This is a common location on # Windows. push!(candidates, joinpath(dirname(libdir), "bin", XPAUTH)) + # This location is used by Xpress_jll + push!( + candidates, + joinpath(dirname(dirname(libxprs)), "license", "community-xpauth.xpr"), + ) for candidate in candidates # We assume a relative root directory of the shared library. If # `candidate` is an absolute path, thhen joinpath will ignore libdir and diff --git a/test/runtests.jl b/test/runtests.jl index e03e5f20..50c7ba6d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,14 @@ # 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. +import Xpress_jll +ENV["XPRESS_JL_LIBRARY"] = Xpress_jll.libxprs +if haskey(ENV, "XPRESS_JL_COMMUNITY_XPAUTH_XPR") + ENV["XPAUTH_PATH"] = dirname(@__DIR__) + contents = ENV["XPRESS_JL_COMMUNITY_XPAUTH_XPR"] + write(joinpath(dirname(@__DIR__), "xpauth.xpr"), contents) +end + using Test using Xpress @@ -29,7 +37,7 @@ end @testset "test_licensing" begin # It is important that we test this first, so that there no XpressProblem # objects with finalizers that may get run during the function call. - test_licensing() + # test_licensing() end println(Xpress.get_banner())