Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite C wrapper using Clang #346

Merged
merged 19 commits into from
Sep 28, 2020
Merged
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ Manifest.toml
perf/*/*.json
perf/*/*.toml
perf/Project.toml
scripts/Project.toml
scripts/*.h
13 changes: 4 additions & 9 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
name = "Gurobi"
uuid = "2e9cd046-0924-5485-92f1-d5272153d98b"
repo = "https://github.com/jump-dev/Gurobi.jl"
version = "0.8.1"
version = "0.9.0"

[deps]
CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82"
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
MathProgBase = "fdba3010-5040-5b88-9595-932c9decdf73"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[compat]
Compat = "2, 3"
CEnum = "0.3, 0.4"
MathOptInterface = "~0.9.14"
MathProgBase = "~0.5.0, ~0.6, ~0.7"
julia = "1"

[extras]
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Compat", "Pkg", "Random", "Test"]
test = ["Random", "Test"]
669 changes: 175 additions & 494 deletions README.md

Large diffs are not rendered by default.

101 changes: 50 additions & 51 deletions deps/build.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
if haskey(ENV, "GITHUB_ACTIONS")
# We're being run as part of a Github action. The most likely case is that
# this is the auto-merge action as part of the General registry.
# For now, skip the installation.
@info("Detected a Github action. Skipping installation.")
exit(0)
end

using Libdl

const DEPS_FILE = joinpath(@__DIR__, "deps.jl")
Expand All @@ -15,21 +7,13 @@ if isfile(DEPS_FILE)
end

function write_depsfile(path)
if Sys.iswindows()
# When `path` gets written out to a file, it will escape any
# backslashes, so we need to doubly escape them. If your path uses
# forward slashes, this operation won't do anything.
path = replace(path, "\\" => "\\\\")
end
open(DEPS_FILE, "w") do io
println(io, "const libgurobi = \"$(path)\"")
println(io, "const libgurobi = \"$(escape_string(path))\"")
end
end

const ALIASES = [
"gurobi90",
"gurobi81", "gurobi80",
"gurobi75", "gurobi70"
]

paths_to_try = copy(ALIASES)
Expand Down Expand Up @@ -63,31 +47,51 @@ for l in paths_to_try
end
end

function _print_GUROBI_HOME_help()
println("""
You should set the `GUROBI_HOME` environment variable to point to the
install location then try again. For example (updating the path to the
correct location if needed):
```
# On Windows, this might be
ENV["GUROBI_HOME"] = "C:\\\\Program Files\\\\gurobi902\\\\win64\\\\"
import Pkg
Pkg.add("Gurobi")
Pkg.build("Gurobi")

# On OSX, this might be
ENV["GUROBI_HOME"] = "/Library/gurobi902/mac64/"
import Pkg
Pkg.add("Gurobi")
Pkg.build("Gurobi")

# On Unix, this might be
ENV["GUROBI_HOME"] = "/opt/gurobi902/linux64/"
import Pkg
Pkg.add("Gurobi")
Pkg.build("Gurobi")
```
""")
end

function diagnose_gurobi_install()
println("""
Unable to locate Gurobi installation. Running some common diagnostics.

**Unable to locate Gurobi installation. Running some common diagnostics.**

Gurobi.jl only supports the following versions:
""")
println.(" - ", ALIASES)
println("""

Did you download and install one of these versions from gurobi.com?

""")
if haskey(ENV, "GUROBI_HOME")
dir = joinpath(ENV["GUROBI_HOME"], Sys.isunix() ? "lib" : "bin")
println("""
Found GUROBI_HOME = $(ENV["GUROBI_HOME"])

Does this point to the correct install location?
- on Windows, this might be `C:\\Program Files\\gurobi810\\win64\\`
- alternatively, on Windows, this might be `C:/Program Files/gurobi810/win64/`
- on OSX, this might be `/Library/gurobi810/mac64/`
- on Unix, this might be `/home/my_user/gurobi810/linux64/`

Note: this has to be a full path, not a path relative to your current
directory or your home directory.

We're going to look for the Gurobi library in this directory:
$(dir)
Expand All @@ -101,50 +105,45 @@ function diagnose_gurobi_install()
println("""

We were looking for (but could not find) a file named like
`libgurobiXXX.so`, `libgurobiXXX.dylib`, or `gurobiXXX.dll`. You
should update your GUROBI_HOME environment variable to point to the
correct location.
""")
`libgurobiXXX.so`, `libgurobiXXX.dylib`, or `gurobiXXX.dll`.\n\n""")

_print_GUROBI_HOME_help()
catch ex
if typeof(ex) <: SystemError
println("""
Aha! We tried looking in `$(dir)`, but something went wrong. Are
you sure that your GUROBI_HOME environment variable is correct?
When combined with the appropriate suffix (e.g., `lib` or
`bin`, it needs to point to a valid directory.
""")
`bin`, it needs to point to a valid directory.\n\n""")
_print_GUROBI_HOME_help()
else
rethrow(ex)
end
end
else
try
println("""
Looking for a version of Gurobi in your path:
""")
# Try to call `gurobi_cl`. This should work if Gurobi is on the
# system path. If it succeeds, it will print out the version.
run(`gurobi_cl --version`)
io = IOBuffer()
run(pipeline(`gurobi_cl --version`; stdout = io))
seekstart(io)
println("""

We couldn't find the `GUROBI_HOME` environment variable, but we
found this version of Gurobi on your path. Is this version one of
the supported versions listed above? If not, you should edit your
`PATH` to point to the correct version.
""")
found this version of Gurobi on your `PATH`.

$(read(io, String))
Is this version one of the supported versions listed above? If so,
we found the executable, but not the libraries we need. Follow the
advice below to set the `GUROBI_HOME` environment variable. If not,
you should edit your `PATH` to point to the correct version, or set
the `GUROBI_HOME` environment variable.\n""")
_print_GUROBI_HOME_help()
catch
println("""

We could not find a version of Gurobi in your path, and we could
not find the environment variable `GUROBI_HOME`. You should set
the `GUROBI_HOME` environment variable to point to the install
location. For example:
- on Windows, this might be `C:\\Program Files\\gurobi810\\win64\\`
- on OSX, this might be `/Library/gurobi810/mac64/`
- on Unix, this might be `/opt/gurobi810/linux64/`

Alternatively, you can add the Gurobi install directory to your path.
""")
We could not find a version of Gurobi in your `PATH`, and we could
not find the environment variable `GUROBI_HOME`.\n\n""")
_print_GUROBI_HOME_help()
end
end
end
Expand Down
39 changes: 39 additions & 0 deletions scripts/clang.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# TODO(odow):
#
# This script can be used to build the C interface to Gurobi. However, it requires
# you to manually do the following steps first:
#
# 1) Copy gurobi_c.h from Gurobi into this /scripts directory

import Clang

const LIBGRB_HEADERS = [
joinpath(@__DIR__, "gurobi_c.h"),
]

const GEN_DIR = joinpath(dirname(@__DIR__), "src", "gen")

wc = Clang.init(
headers = LIBGRB_HEADERS,
output_file = joinpath(GEN_DIR, "libgrb_api.jl"),
common_file = joinpath(GEN_DIR, "libgrb_common.jl"),
header_wrapped = (root, current) -> root == current,
header_library = x -> "libgurobi",
clang_diagnostics = true,
)

run(wc)

function manual_corrections()
filename = joinpath(GEN_DIR, "libgrb_api.jl")
lines = readlines(filename; keep = true)
for (i, line) in enumerate(lines)
lines[i] = replace(line, "Cstring" => "Ptr{Cchar}")
end
open(filename, "w") do io
print.(Ref(io), lines)
end
end
manual_corrections()

rm(joinpath(dirname(@__DIR__), "src", "gen", "LibTemplate.jl"))
54 changes: 54 additions & 0 deletions scripts/deprecate.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# This file was used to create the list of deprecations when moving from v0.8.1
# to v0.9.0.

using Gurobi

io = open("deprecated_functions.jl", "w")
print(io, """
const _DEPRECATED_ERROR_MESSAGE = \"\"\"
The C API of Gurobi.jl has been rewritten to expose the complete C API, and
all old functions have been removed.

For example:

model = Gurobi.Optimizer()
stat = Gurobi.get_status_code(model.inner)

is now:

model = Gurobi.Optimizer()
valueP = Ref{Cint}()
ret = GRBgetintattr(model, "Status", valueP)
if ret != 0
# Do something because the call failed
end
stat = valueP[]

The new API is more verbose, but the names and function arguments are now
identical to the C API, documentation for which is available at:
https://www.gurobi.com/documentation/9.0/refman/c_api_details.html

To revert to the old API, use:

import Pkg
Pkg.add(Pkg.PackageSpec(name = \"Gurobi\", version = v\"0.8.1\"))

Then restart Julia for the change to take effect.
\"\"\"
""")

exported_names = Base.names(Gurobi; all = false)
for name in Base.names(Gurobi; all = true)
foo = getfield(Gurobi, name)
if !(foo isa Function)
continue
elseif any(startswith.(Ref(string(foo)), ["#", "@", "_"]))
continue
end
println(io, "$(foo)(args...; kwargs...) = error(_DEPRECATED_ERROR_MESSAGE)")
if name in exported_names
println(io, "export $(foo)")
end
println(io)
end
close(io)
Loading