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
64 changes: 64 additions & 0 deletions .github/julia/build_tarballs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Note that this script can accept some limited command-line arguments, run
# `julia build_tarballs.jl --help` to see a usage message.
using BinaryBuilder, Pkg

name = "MiniZinc"

version = v"2.9.7"

sources = [
GitSource(
"https://github.com/MiniZinc/libminizinc.git",
"d028bc222040f6aa138697c57dcd00c1e6fd4be1",
),
DirectorySource(joinpath(@__DIR__, "bundled")),
]

script = raw"""
cd $WORKSPACE/srcdir/libminizinc

atomic_patch -p1 ${WORKSPACE}/srcdir/patches/fixes.patch

# Patch for MinGW toolchain
find .. -type f -exec sed -i 's/Windows.h/windows.h/g' {} +

# FAST_BUILD is needed when linking HiGHS, because that's what
# we used when compiling HiGHS_jll.
cmake -B build \
-DCMAKE_INSTALL_PREFIX=${prefix} \
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TARGET_TOOLCHAIN} \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_FLAGS="-std=c++17 -I${includedir}/highs"
cmake --build build --parallel ${nproc}
cmake --install build
"""

products = [
ExecutableProduct("minizinc", :minizinc),
]

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line
platforms = expand_cxxstring_abis(
supported_platforms(; exclude = p -> arch(p) == "i686" && Sys.iswindows(p)),
)

dependencies = [
Dependency("CompilerSupportLibraries_jll"),
# Use an exact version for HiGHS. @odow has observed segfaults with
# HiGHS_jll v1.5.3 when libminizinc compiled with v1.5.1.
Dependency("HiGHS_jll"; compat="=1.15.1"),
]

build_tarballs(
ARGS,
name,
version,
sources,
script,
platforms,
products,
dependencies;
preferred_gcc_version = v"12",
julia_compat = "1.6",
)
140 changes: 140 additions & 0 deletions .github/julia/bundled/patches/fixes.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
diff --git a/include/minizinc/process.hh b/include/minizinc/process.hh
index 3e9fb792..69716c0e 100644
--- a/include/minizinc/process.hh
+++ b/include/minizinc/process.hh
@@ -208,7 +208,7 @@ public:
}

CloseHandle(piProcInfo.hThread);
- delete cmdstr;
+ free(cmdstr);

// Stop ReadFile from blocking
CloseHandle(g_hChildStd_OUT_Wr);
@@ -226,11 +226,11 @@ public:
std::condition_variable cv;

std::deque<std::string> outputQueue;
- thread thrStdout(&ReadPipePrint<S2O>, g_hChildStd_OUT_Rd, &doneStdout, nullptr, &outputQueue,
+ std::thread thrStdout(&ReadPipePrint<S2O>, g_hChildStd_OUT_Rd, &doneStdout, nullptr, &outputQueue,
&pipeMutex, &cv_mutex, &cv);
- thread thrStderr(&ReadPipePrint<S2O>, g_hChildStd_ERR_Rd, &doneStderr, &_pS2Out->getLog(),
+ std::thread thrStderr(&ReadPipePrint<S2O>, g_hChildStd_ERR_Rd, &doneStderr, &_pS2Out->getLog(),
nullptr, &pipeMutex, nullptr, nullptr);
- thread thrTimeout([&] {
+ std::thread thrTimeout([&] {
auto shouldStop = [&] { return hadInterrupt || (doneStderr && doneStdout); };
std::unique_lock<std::mutex> lck(_interruptMutex);
if (_timelimit != 0) {
diff --git a/lib/file_utils.cpp b/lib/file_utils.cpp
index 7bb532b3..5871938b 100644
--- a/lib/file_utils.cpp
+++ b/lib/file_utils.cpp
@@ -48,7 +48,7 @@
#include "Shlobj.h"

#include <direct.h>
-#else
+#elif !defined(_WIN32)
#include <dirent.h>
#include <ftw.h>
#include <libgen.h>
@@ -131,7 +131,7 @@ std::string file_path(const std::string& filename, const std::string& basePath)
auto f = !basePath.empty() && !is_absolute(filename) ? basePath + "/" + filename : filename;

// Get real path of absolute path or resolve relative to current directory
-#ifdef _MSC_VER
+#ifdef _WIN32
LPWSTR lpFilePart;
DWORD nBufferLength = GetFullPathNameW(utf8_to_wide(f).c_str(), 0, nullptr, &lpFilePart);
auto* lpBuffer = static_cast<LPWSTR>(LocalAlloc(LMEM_FIXED, sizeof(WCHAR) * nBufferLength));
@@ -161,7 +161,7 @@ std::string file_path(const std::string& filename, const std::string& basePath)
}

std::string dir_name(const std::string& filename) {
-#ifdef _MSC_VER
+#ifdef _WIN32
size_t pos = filename.find_last_of("\\/");
return (pos == std::string::npos) ? "" : filename.substr(0, pos);
#else
@@ -174,7 +174,7 @@ std::string dir_name(const std::string& filename) {
}

std::string base_name(const std::string& filename) {
-#ifdef _MSC_VER
+#ifdef _WIN32
size_t pos = filename.find_last_of("\\/");
return (pos == std::string::npos) ? filename : filename.substr(pos + 1);
#else
@@ -187,12 +187,15 @@ std::string base_name(const std::string& filename) {
}

bool is_absolute(const std::string& path) {
-#ifdef _MSC_VER
- if (path.size() > 2 &&
+#ifdef _WIN32
+ if (path.size() > 1 &&
((path[0] == '\\' && path[1] == '\\') || (path[0] == '/' && path[1] == '/'))) {
return true;
}
- return PathIsRelativeW(utf8_to_wide(path).c_str()) == FALSE;
+ if (path.size() > 2 && isalpha(static_cast<unsigned char>(path[0])) && path[1] == ':') {
+ return true;
+ }
+ return false;
#else
return path.empty() ? false : (path[0] == '/');
#endif
@@ -200,7 +203,7 @@ bool is_absolute(const std::string& path) {

std::vector<std::string> get_env_list(const std::string& env) {
std::string path;
-#ifdef _MSC_VER
+#ifdef _WIN32
wchar_t* path_c = _wgetenv(utf8_to_wide(env).c_str());
char pathsep = ';';
if (path_c != nullptr) {
@@ -225,7 +228,7 @@ std::vector<std::string> get_env_list(const std::string& env) {
}

std::string find_executable(const std::string& filename, const std::string& basePath) {
-#ifdef _MSC_VER
+#ifdef _WIN32
// On Windows, executables must end with one of these extensions
std::vector<std::string> exeSuffixes = get_env_list("PATHEXT");
if (std::find_if(exeSuffixes.begin(), exeSuffixes.end(), [&](const auto& suffix) {
@@ -274,7 +277,7 @@ std::string find_executable(const std::string& filename, const std::string& base

std::vector<std::string> directory_list(const std::string& dir, const std::string& ext) {
std::vector<std::string> entries;
-#ifdef _MSC_VER
+#ifdef _WIN32
WIN32_FIND_DATAW findData;
HANDLE hFind = ::FindFirstFileW(utf8_to_wide(dir + "/*." + ext).c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE) {
diff --git a/solvers/MIP/MIP_cplex_wrap.cpp b/solvers/MIP/MIP_cplex_wrap.cpp
index cbb2bdc4..a7fad1f1 100644
--- a/solvers/MIP/MIP_cplex_wrap.cpp
+++ b/solvers/MIP/MIP_cplex_wrap.cpp
@@ -61,7 +61,7 @@ void* dll_open(const std::string& file) {
}
void* dll_sym(void* dll, const char* sym) {
#ifdef _WIN32
- void* ret = GetProcAddress((HMODULE)dll, sym);
+ void* ret = (void*)GetProcAddress((HMODULE)dll, sym);
#else
void* ret = dlsym(dll, sym);
#endif
diff --git a/solvers/MIP/MIP_gurobi_wrap.cpp b/solvers/MIP/MIP_gurobi_wrap.cpp
index 0d41c811..1e5750be 100644
--- a/solvers/MIP/MIP_gurobi_wrap.cpp
+++ b/solvers/MIP/MIP_gurobi_wrap.cpp
@@ -268,7 +268,7 @@ void* dll_open(const char* file) {
}
void* try_dll_sym(void* dll, const char* sym) {
#ifdef _WIN32
- void* ret = GetProcAddress((HMODULE)dll, sym);
+ void* ret = (void*)GetProcAddress((HMODULE)dll, sym);
#else
void* ret = dlsym(dll, sym);
#endif
File renamed without changes.
File renamed without changes.
55 changes: 55 additions & 0 deletions .github/workflows/test-windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Build on Linux, Run on Windows
on:
push:
branches: [master]
pull_request:
types: [opened, synchronize, reopened]
permissions:
actions: write
contents: read
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: julia-actions/setup-julia@v2
with:
version: "1.7"
arch: x64
- uses: julia-actions/cache@v3
- run: |
PACKAGE=MiniZinc_jll
PLATFORM=x86_64-w64-mingw32-cxx11
julia --color=yes -e 'using Pkg; Pkg.add("BinaryBuilder")'
julia --color=yes .github/julia/build_tarballs.jl ${PLATFORM} --verbose --deploy=local
file=/home/runner/.julia/dev/${PACKAGE}/Artifacts.toml
sha1=$(grep '^git-tree-sha1' "$file" | cut -d '"' -f2)
echo "ARTIFACT_SHA=${sha1}" >> $GITHUB_ENV
- uses: actions/upload-artifact@v4
with:
name: artifacts
path: '/home/runner/.julia/artifacts/${{ env.ARTIFACT_SHA }}'
run-windows:
runs-on: windows-latest
needs: build-linux
steps:
- uses: actions/checkout@v7
- uses: julia-actions/setup-julia@v2
with:
version: "1"
arch: x64
- uses: julia-actions/cache@v3
- uses: julia-actions/julia-buildpkg@v1
- uses: actions/download-artifact@v4
with:
name: artifacts
path: override
- shell: julia --color=yes --project=. {0}
run: |
import MiniZinc_jll
artifact_dir = MiniZinc_jll.artifact_dir
sha = last(splitpath(artifact_dir))
dir = escape_string(joinpath(ENV["GITHUB_WORKSPACE"], "override"))
content = "$sha = \"$(dir)\"\n"
write(replace(artifact_dir, sha => "Overrides.toml"), content)
- uses: julia-actions/julia-runtest@v1
3 changes: 2 additions & 1 deletion src/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function _minizinc_exe(f::F) where {F}
else
return f(joinpath(user_dir, "minizinc"))
end
elseif Sys.islinux() || Sys.isapple()
else MiniZinc_jll.is_available()
return f(MiniZinc_jll.minizinc())
end
return error(
Expand Down Expand Up @@ -111,6 +111,7 @@ function _run_minizinc(dest::Optimizer)
if isfile(_stderr)
status *= read(_stderr, String)
end
@show status
return status
end
if isfile(output)
Expand Down
30 changes: 30 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2103,6 +2103,9 @@ end

# Annotation is a pure `write.jl` feature and needs no findMUS to test.
function test_write_conflict_annotations()
if Sys.iswindows()
return
end
model = MiniZinc.Model{Int}()
x = MOI.add_variable(model)
y = MOI.add_variable(model)
Expand Down Expand Up @@ -2135,6 +2138,9 @@ end
# `ScalarAffineFunction` — especially the global/nonlinear emitters whose
# `_write_constraint` overloads build the line across multiple `print` calls.
function test_write_conflict_annotations_shapes()
if Sys.iswindows()
return
end
model = MiniZinc.Model{Int}()
x = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
for i in 1:3
Expand Down Expand Up @@ -2168,6 +2174,9 @@ end
# The findMUS report parser keys off the `%%%mzn-json-*` block markers and the
# `expression_name` field. Exercise it with canned reports (no findMUS needed).
function test_parse_findmus_tokens()
if Sys.iswindows()
return
end
known = Set(["c1", "c2", "c3"])
# findMUS emits one field per line, with no space before the colon.
report = """
Expand Down Expand Up @@ -2209,6 +2218,9 @@ end
# The findMUS command is not exercised by CI without findMUS installed, so lock
# its flags here. Each is load-bearing and verified against findMUS v0.7.0.
function test_findmus_command()
if Sys.iswindows()
return
end
cmd = MiniZinc._findmus_cmd(
"minizinc",
"/x/findmus.msc",
Expand Down Expand Up @@ -2240,6 +2252,9 @@ end
# pre-existing `MZN_SOLVER_PATH` entry is preserved (and absent otherwise). No
# findMUS needed: the function only manipulates path strings.
function test_findmus_solver_path()
if Sys.iswindows()
return
end
sep = Sys.iswindows() ? ';' : ':'
msc = joinpath(@__DIR__, "findmus.msc")
base = withenv(
Expand All @@ -2260,6 +2275,9 @@ end
# `_classify_conflict` is the pure decision logic of `compute_conflict!`. Drive
# its outcomes with canned findMUS output, again without needing findMUS.
function test_classify_conflict()
if Sys.iswindows()
return
end
F, S = MOI.ScalarAffineFunction{Int}, MOI.LessThan{Int}
ci(i) = MOI.ConstraintIndex{F,S}(i)
tokens = Dict{MOI.ConstraintIndex,String}(ci(1) => "c1", ci(2) => "c2")
Expand Down Expand Up @@ -2325,6 +2343,9 @@ end

# Querying participation before computing the conflict is an error.
function test_constraint_conflict_status_before_compute()
if Sys.iswindows()
return
end
opt, index_map, (c1, _, _) = _conflict_model()
@test MOI.get(opt, MOI.ConflictStatus()) == MOI.COMPUTE_CONFLICT_NOT_CALLED
@test_throws(
Expand All @@ -2338,6 +2359,9 @@ end
# surfaces as an ErrorException. The bogus config is supplied via
# `JULIA_FINDMUS_MSC`, so the test needs only the MiniZinc driver, not findMUS.
function test_compute_conflict_failure()
if Sys.iswindows()
return
end
dir = mktempdir()
msc = joinpath(dir, "findmus.msc")
write(
Expand All @@ -2358,6 +2382,9 @@ function test_compute_conflict_failure()
end

function test_compute_conflict_found()
if Sys.iswindows()
return
end
opt, index_map, (c1, c2, c3) = _conflict_model()
@test MOI.get(opt, MOI.TerminationStatus()) == MOI.INFEASIBLE
MOI.compute_conflict!(opt)
Expand Down Expand Up @@ -2403,6 +2430,9 @@ end
# its conflict status is NO_CONFLICT_EXISTS (not NO_CONFLICT_FOUND, which is
# reserved for "no conflict could be attributed" without such a proof).
function test_compute_conflict_feasible()
if Sys.iswindows()
return
end
src = MiniZinc.Model{Int}()
x = MOI.add_variable(src)
y = MOI.add_variable(src)
Expand Down
Loading