Skip to content

Commit

Permalink
Refactor src/license.jl (#251)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored Mar 28, 2024
1 parent ceb9df8 commit 1b075d1
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 99 deletions.
37 changes: 32 additions & 5 deletions src/Xpress.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,39 @@ include("helper.jl")
include("api.jl")
include("license.jl")

function initialize()
Libdl.dlopen(libxprs)
userlic()
"""
initialize(;
liccheck::Function = identity,
verbose::Bool = true,
xpauth_path::String = ""
)
Performs license checking with `liccheck` validation function on dir
`xpauth_path`.
This function must be called before any XPRS functions can be called.
By default, `__init__` calls this with no keyword arguments.
## Example
```julia
ENV["XPRESS_JL_NO_AUTO_INIT"] = true
using Xpress
liccheck(x::Vector{Cint}) = Cint[xor(x[1], 0x0123)]
Xpress.initialize(;
liccheck = liccheck,
verbose = false,
xpauth_path = "/tmp/xpauth.xpr,
)
# Now you can use Xpress
```
"""
function initialize(; kwargs...)
userlic(; kwargs...)
Lib.XPRSinit(C_NULL)
# Calling free is not necessary since destroyprob is called
# in the finalizer.
# Calling XPRSfree is not necessary since XPRSdestroyprob is called in the
# finalizer.
return
end

Expand Down
152 changes: 58 additions & 94 deletions src/license.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,114 +3,78 @@
# 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.

# lic checking file
# -----------------

# license check empty function
# ----------------------------
function emptyliccheck(lic::Vector{Cint})
return lic
end

function touchlic(path)
f = open(path)
return close(f)
end

function get_xpauthpath(xpauth_path = "", verbose::Bool = true)
function _get_xpauthpath(xpauth_path = "", verbose::Bool = true)
# The directory of the xprs shared object. This is the root from which we
# search for licenses.
libdir = dirname(libxprs)
XPAUTH = "xpauth.xpr"

candidates = []

# user sent the complete path
push!(candidates, xpauth_path)

# user sent directory
push!(candidates, joinpath(xpauth_path, XPAUTH))

# default env (not metioned in manual)
if haskey(ENV, "XPAUTH_PATH")
xpauth_path = replace(ENV["XPAUTH_PATH"], "\"" => "")
push!(candidates, joinpath(xpauth_path, XPAUTH))
end

# default lib dir
if haskey(ENV, "XPRESSDIR")
xpressdir = replace(ENV["XPRESSDIR"], "\"" => "")
push!(candidates, joinpath(xpressdir, "bin", XPAUTH))
# Search in absolute path given by the user (assuming it was a file), and as
# a directory by appending XPAUTH.
candidates = [xpauth_path, joinpath(xpauth_path, XPAUTH)]
# Search in the directories of the XPAUTH_PATH and XPRESSDIR environnment
# variables.
for key in ("XPAUTH_PATH", "XPRESSDIR")
if haskey(ENV, key)
push!(candidates, joinpath(replace(ENV[key], "\"" => ""), XPAUTH))
end
end

# user´s lib dir
push!(candidates, joinpath(dirname(dirname(libxprs)), "bin", XPAUTH))

for i in candidates
if isfile(i)
# Search in `xpress/lib/../bin/xpauth.xpr`. This is a common location on
# Windows.
push!(candidates, joinpath(dirname(libdir), "bin", XPAUTH))
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
# return candidate.
filename = joinpath(libdir, candidate)
# If the file exists, we assume it is a license. We don't attempt to
# validate the contents of the file.
if isfile(filename)
if verbose && !haskey(ENV, "XPRESS_JL_NO_INFO")
@info("Xpress: Found license file $i")
@info("Xpress: Found license file $filename")
end
return i
return filename
end
end

return error(
"Could not find xpauth.xpr license file. Check XPRESSDIR or XPAUTH_PATH environment variables.",
"Could not find xpauth.xpr license file. Set the `XPRESSDIR` or " *
"`XPAUTH_PATH` environment variables.",
)
end
"""
userlic(; liccheck::Function = emptyliccheck, xpauth_path::String = "" )
Performs license chhecking with `liccheck` validation function on dir `xpauth_path`
"""

# Keep `userlic` for backwards compatibility. PSR have a customized setup for
# managing licenses.
#
# New users should use `Xpress.initialize`.
function userlic(;
liccheck::Function = identity,
verbose::Bool = true,
liccheck::Function = emptyliccheck,
xpauth_path::String = "",
)

# change directory to reach all libs
initdir = pwd()
if isdir(dirname(libxprs))
cd(dirname(libxprs))
end

# open and free xpauth.xpr (touches the file to release it)
path_lic = get_xpauthpath(xpauth_path, verbose)
touchlic(path_lic)

# pre allocate vars
lic = Cint[1]
slicmsg = path_lic #xpauth_path == "dh" ? Array{Cchar}(undef, 1024*8) :

# FIRST call do xprslicense to get BASE LIC
Lib.XPRSlicense(lic, slicmsg)

# convert BASE LIC to GIVEN LIC
lic = liccheck(lic)

# Send GIVEN LIC to XPRESS lib
buffer = Array{Cchar}(undef, 1024 * 8)
buffer_p = pointer(buffer)
ierr = GC.@preserve buffer begin
Lib.XPRSlicense(lic, Cstring(buffer_p))
end

# check LIC TYPE
if ierr == 16
# DEVELOPER
if verbose && !haskey(ENV, "XPRESS_JL_NO_INFO")
@info("Xpress: Development license detected.")
end
elseif ierr != 0
# FAIL
verbose &= !haskey(ENV, "XPRESS_JL_NO_INFO")
path_lic = _get_xpauthpath(xpauth_path, verbose)
# Pre-allocate storage for the license integer. For backward compatibility,
# we use `Vector{Cint}`, because some users may have `liccheck` functions
# which rely on this.
license = Cint[1]
# First, call XPRSlicense to populate `license` with an integer. We don't
# check the return code.
_ = Lib.XPRSlicense(license, path_lic)
# Then, for some licenses, we need to modify the license integer by a
# secret password.
license = liccheck(license)
# Now, we need to send the password back to Xpress.
err = Lib.XPRSlicense(license, path_lic)
if !(err == 16 || err == 0)
@info("Xpress: Failed to find working license.")
error(getlicerrmsg())
else
# USER
if verbose && !haskey(ENV, "XPRESS_JL_NO_INFO")
@info("Xpress: User license detected.")
@info(unsafe_string(pointer(slicmsg)))
buffer = Vector{Cchar}(undef, 1024 * 8)
p_buffer = pointer(buffer)
GC.@preserve buffer begin
Lib.XPRSgetlicerrmsg(p_buffer, 1024)
throw(XpressError(err, unsafe_string(p_buffer)))
end
elseif verbose
type = err == 16 ? "Development" : "User"
@info("Xpress: $type license detected.")
end

# go back to initial folder
return cd(initdir)
return
end
26 changes: 26 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,32 @@
using Test
using Xpress

function test_licensing()
# Create a bogus license file
xpauth_path = mktempdir()
filename = joinpath(xpauth_path, "xpauth.xpr")
write(filename, "bogus_license")
# Test that passing `""` can find our current license. This should already
# be the case because we managed to install and start the tests...
@test isfile(Xpress._get_xpauthpath("", false))
# Test that using the test directory cannot find a license.
@test_throws ErrorException Xpress._get_xpauthpath(@__DIR__, false)
# Now're going to test checking for new licenses. To do so, we first need to
# free the current one:
Xpress.Lib.XPRSfree()
# Then, we can check that using the root fails to find a license
@test_throws Xpress.XpressError Xpress.initialize(; xpauth_path)
# Now we need to re-initialize the license so that we can run other tests.
Xpress.initialize()
return
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()
end

println(Xpress.get_banner())
println("Optimizer version: $(Xpress.get_version())")

Expand Down

0 comments on commit 1b075d1

Please sign in to comment.