1 /* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2
3 /*
4 * Main authors:
5 * Guido Tack <guido.tack@monash.edu>
6 */
7
8 /* This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11
12 #include <minizinc/file_utils.hh>
13 #include <minizinc/json_parser.hh>
14 #include <minizinc/parser.hh>
15 #include <minizinc/prettyprinter.hh>
16 #include <minizinc/solver_config.hh>
17
18 #include <algorithm>
19 #include <cctype>
20 #include <iterator>
21 #include <set>
22 #include <sstream>
23 #include <string>
24
25 using namespace std;
26
27 namespace MiniZinc {
28
validate(const std::string & v) const29 bool SolverConfig::ExtraFlag::validate(const std::string& v) const {
30 try {
31 switch (flagType) {
32 case FlagType::T_BOOL:
33 case FlagType::T_STRING:
34 return range.empty() || std::find(range.begin(), range.end(), v) != range.end();
35 case FlagType::T_INT: {
36 long long i = stoll(v);
37 return range.empty() || (i >= stoll(range[0]) && i <= stoll(range[1]));
38 }
39 case FlagType::T_FLOAT: {
40 double i = stod(v);
41 return range.empty() || (i >= stod(range[0]) && i <= stod(range[1]));
42 }
43 }
44 } catch (const invalid_argument&) {
45 return false;
46 } catch (const out_of_range&) {
47 return false;
48 }
49 return false;
50 }
51
52 namespace {
get_string(AssignI * ai)53 std::string get_string(AssignI* ai) {
54 if (auto* sl = ai->e()->dynamicCast<StringLit>()) {
55 return std::string(sl->v().c_str(), sl->v().size());
56 }
57 throw ConfigException("invalid configuration item (right hand side must be string)");
58 }
get_bool(AssignI * ai)59 bool get_bool(AssignI* ai) {
60 if (auto* bl = ai->e()->dynamicCast<BoolLit>()) {
61 return bl->v();
62 }
63 throw ConfigException("invalid configuration item (right hand side must be bool)");
64 }
get_int(AssignI * ai)65 int get_int(AssignI* ai) {
66 if (auto* il = ai->e()->dynamicCast<IntLit>()) {
67 return static_cast<int>(il->v().toInt());
68 }
69 throw ConfigException("invalid configuration item (right hand side must be int)");
70 }
get_string_list(AssignI * ai)71 std::vector<std::string> get_string_list(AssignI* ai) {
72 if (auto* al = ai->e()->dynamicCast<ArrayLit>()) {
73 std::vector<std::string> ret;
74 for (unsigned int i = 0; i < al->size(); i++) {
75 if (auto* sl = (*al)[i]->dynamicCast<StringLit>()) {
76 ret.emplace_back(sl->v().c_str(), sl->v().size());
77 } else {
78 throw ConfigException(
79 "invalid configuration item (right hand side must be a list of strings)");
80 }
81 }
82 return ret;
83 }
84 throw ConfigException("invalid configuration item (right hand side must be a list of strings)");
85 }
get_string_pair_list(AssignI * ai)86 std::vector<std::pair<std::string, std::string> > get_string_pair_list(AssignI* ai) {
87 if (auto* al = ai->e()->dynamicCast<ArrayLit>()) {
88 std::vector<std::pair<std::string, std::string> > ret;
89 if (al->dims() != 2 || al->min(1) != 1 || al->max(1) != 2) {
90 throw ConfigException(
91 "invalid configuration item (right hand side must be a 2d array of strings)");
92 }
93 for (unsigned int i = 0; i < al->size(); i += 2) {
94 auto* sl1 = (*al)[i]->dynamicCast<StringLit>();
95 auto* sl2 = (*al)[i + 1]->dynamicCast<StringLit>();
96 if ((sl1 != nullptr) && (sl2 != nullptr)) {
97 ret.emplace_back(std::string(sl1->v().c_str(), sl1->v().size()),
98 std::string(sl2->v().c_str(), sl2->v().size()));
99 } else {
100 throw ConfigException(
101 "invalid configuration item (right hand side must be a 2d array of strings)");
102 }
103 }
104 return ret;
105 }
106 throw ConfigException(
107 "invalid configuration item (right hand side must be a 2d array of strings)");
108 }
get_default_option_list(AssignI * ai)109 std::vector<std::vector<std::string> > get_default_option_list(AssignI* ai) {
110 if (auto* al = ai->e()->dynamicCast<ArrayLit>()) {
111 std::vector<std::vector<std::string> > ret;
112 if (al->size() == 0) {
113 return ret;
114 }
115 if (al->dims() != 2) {
116 throw ConfigException(
117 "invalid configuration item (right hand side must be a 2d array of strings)");
118 }
119 int nCols = al->max(1) - al->min(1) + 1;
120 if (nCols != 3) {
121 throw ConfigException(
122 "invalid configuration item (right hand side must be a 2d array of strings with 3 "
123 "columns)");
124 }
125 for (unsigned int i = 0; i < al->size(); i += nCols) {
126 auto* sl0 = (*al)[i]->dynamicCast<StringLit>();
127 auto* sl1 = (*al)[i + 1]->dynamicCast<StringLit>();
128 auto* sl2 = (*al)[i + 2]->dynamicCast<StringLit>();
129 if ((sl0 != nullptr) && (sl1 != nullptr) && (sl2 != nullptr)) {
130 ret.push_back(std::vector<std::string>({std::string(sl0->v().c_str(), sl0->v().size()),
131 std::string(sl1->v().c_str(), sl1->v().size()),
132 std::string(sl2->v().c_str(), sl2->v().size())}));
133 } else {
134 throw ConfigException(
135 "invalid configuration item (right hand side must be a list of strings)");
136 }
137 }
138 return ret;
139 }
140 throw ConfigException(
141 "invalid configuration item (right hand side must be a 2d array of strings)");
142 }
get_extra_flag_list(AssignI * ai)143 std::vector<SolverConfig::ExtraFlag> get_extra_flag_list(AssignI* ai) {
144 if (auto* al = ai->e()->dynamicCast<ArrayLit>()) {
145 std::vector<SolverConfig::ExtraFlag> ret;
146 if (al->size() == 0) {
147 return ret;
148 }
149 if (al->dims() != 2) {
150 throw ConfigException(
151 "invalid configuration item (right hand side must be a 2d array of strings)");
152 }
153 int nCols = al->max(1) - al->min(1) + 1;
154 if (nCols < 2 || nCols > 4) {
155 throw ConfigException(
156 "invalid configuration item (right hand side must be a 2d array of strings)");
157 }
158 bool haveType = (nCols >= 3);
159 bool haveDefault = (nCols >= 4);
160 for (unsigned int i = 0; i < al->size(); i += nCols) {
161 auto* sl1 = (*al)[i]->dynamicCast<StringLit>();
162 auto* sl2 = (*al)[i + 1]->dynamicCast<StringLit>();
163 StringLit* sl3 = haveType ? (*al)[i + 2]->dynamicCast<StringLit>() : nullptr;
164 StringLit* sl4 = haveDefault ? (*al)[i + 3]->dynamicCast<StringLit>() : nullptr;
165 std::string opt_type_full =
166 sl3 != nullptr ? std::string(sl3->v().c_str(), sl3->v().size()) : "bool";
167 std::vector<std::string> opt_range;
168 std::string opt_def;
169 if (sl4 != nullptr) {
170 opt_def = std::string(sl4->v().c_str(), sl4->v().size());
171 } else if (opt_type_full == "bool") {
172 opt_def = "false";
173 } else if (opt_type_full == "int") {
174 opt_def = "0";
175 } else if (opt_type_full == "float") {
176 opt_def = "0.0";
177 }
178 size_t split = opt_type_full.find(":");
179 std::string opt_type = opt_type_full.substr(0, split);
180 SolverConfig::ExtraFlag::FlagType flag_type;
181 if (opt_type == "bool") {
182 flag_type = SolverConfig::ExtraFlag::FlagType::T_BOOL;
183 } else if (opt_type == "int") {
184 flag_type = SolverConfig::ExtraFlag::FlagType::T_INT;
185 } else if (opt_type == "float") {
186 flag_type = SolverConfig::ExtraFlag::FlagType::T_FLOAT;
187 } else if (opt_type == "string" || opt_type == "opt") {
188 flag_type = SolverConfig::ExtraFlag::FlagType::T_STRING;
189 }
190 if (split != std::string::npos) {
191 opt_type_full = opt_type_full.substr(split + 1);
192 while (!opt_type_full.empty()) {
193 split = opt_type_full.find(":");
194 opt_range.push_back(opt_type_full.substr(0, split));
195 opt_type_full = split == std::string::npos ? "" : opt_type_full.substr(split + 1);
196 }
197 }
198
199 if ((sl1 != nullptr) && (sl2 != nullptr)) {
200 ret.emplace_back(std::string(sl1->v().c_str(), sl1->v().size()),
201 std::string(sl2->v().c_str(), sl2->v().size()), flag_type, opt_range,
202 opt_def);
203 } else {
204 throw ConfigException(
205 "invalid configuration item (right hand side must be a 2d array of strings)");
206 }
207 }
208 return ret;
209 }
210 throw ConfigException(
211 "invalid configuration item (right hand side must be a 2d array of strings)");
212 }
213
get_env(const char * v)214 std::string get_env(const char* v) {
215 std::string ret;
216 #ifdef _MSC_VER
217 size_t len;
218 getenv_s(&len, nullptr, 0, v);
219 if (len > 0) {
220 char* p = static_cast<char*>(malloc(len * sizeof(char)));
221 getenv_s(&len, p, len, v);
222 if (len > 0) {
223 ret = p;
224 }
225 free(p);
226 }
227 #else
228 char* p = getenv(v);
229 if (p != nullptr) {
230 ret = p;
231 }
232 #endif
233 return ret;
234 }
235
char_to_lower(char c)236 char char_to_lower(char c) {
237 return static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
238 }
string_to_lower(std::string s)239 std::string string_to_lower(std::string s) {
240 std::transform(s.begin(), s.end(), s.begin(), char_to_lower);
241 return s;
242 }
243 struct SortByLowercase {
operator ()MiniZinc::__anon1ff463660111::SortByLowercase244 bool operator()(const std::string& n1, const std::string& n2) {
245 for (size_t i = 0; i < n1.size() && i < n2.size(); i++) {
246 if (std::tolower(n1[i]) != std::tolower(n2[i])) {
247 return std::tolower(n1[i]) < std::tolower(n2[i]);
248 }
249 }
250 return n1.size() < n2.size();
251 }
252 };
253 struct SortByName {
254 const std::vector<SolverConfig>& solvers;
255 SortByLowercase sortByLowercase;
SortByNameMiniZinc::__anon1ff463660111::SortByName256 SortByName(const std::vector<SolverConfig>& solvers0) : solvers(solvers0) {}
operator ()MiniZinc::__anon1ff463660111::SortByName257 bool operator()(int idx1, int idx2) {
258 return sortByLowercase(solvers[idx1].name(), solvers[idx2].name());
259 }
260 };
261
262 } // namespace
263
load(const string & filename)264 SolverConfig SolverConfig::load(const string& filename) {
265 SolverConfig sc;
266 sc._configFile = FileUtils::file_path(filename);
267 ostringstream errstream;
268 try {
269 Env confenv;
270 Model* m = nullptr;
271 if (JSONParser::fileIsJSON(filename)) {
272 JSONParser jp(confenv.envi());
273 try {
274 m = new Model;
275 GCLock lock;
276 jp.parse(m, filename, false);
277 } catch (JSONError& e) {
278 delete m;
279 m = nullptr;
280 throw ConfigException(e.msg());
281 }
282 } else {
283 vector<string> filenames;
284 filenames.push_back(filename);
285 m = parse(confenv, filenames, vector<string>(), "", "", vector<string>(), false, true, false,
286 false, errstream);
287 }
288 if (m != nullptr) {
289 bool hadId = false;
290 bool hadVersion = false;
291 bool hadName = false;
292 string basePath = FileUtils::dir_name(sc._configFile);
293 for (auto& i : *m) {
294 if (auto* ai = i->dynamicCast<AssignI>()) {
295 if (ai->id() == "id") {
296 sc._id = get_string(ai);
297 hadId = true;
298 } else if (ai->id() == "name") {
299 sc._name = get_string(ai);
300 hadName = true;
301 } else if (ai->id() == "executable") {
302 std::string exePath = get_string(ai);
303 sc._executable = exePath;
304 std::string exe = FileUtils::find_executable(FileUtils::file_path(exePath, basePath));
305 int nr_found = (int)(!exe.empty());
306 std::string tmp = FileUtils::file_path(FileUtils::find_executable(exePath));
307 nr_found += (int)((!tmp.empty()) && tmp != exe);
308 exe = exe.empty() ? tmp : exe;
309 if (nr_found > 0) {
310 sc._executableResolved = exe;
311 if (nr_found > 1) {
312 std::cerr << "Warning: multiple executables '" << exePath
313 << "' found on the system, using '" << exe << "'" << std::endl;
314 }
315 }
316 } else if (ai->id() == "mznlib") {
317 std::string libPath = get_string(ai);
318 sc._mznlib = libPath;
319 if (!libPath.empty()) {
320 if (libPath[0] == '-') {
321 sc._mznlibResolved = libPath;
322 } else {
323 sc._mznlibResolved = FileUtils::file_path(libPath, basePath);
324 }
325 }
326 } else if (ai->id() == "version") {
327 sc._version = get_string(ai);
328 hadVersion = true;
329 } else if (ai->id() == "mznlibVersion") {
330 sc._mznlibVersion = get_int(ai);
331 } else if (ai->id() == "description") {
332 sc._description = get_string(ai);
333 } else if (ai->id() == "contact") {
334 sc._contact = get_string(ai);
335 } else if (ai->id() == "website") {
336 sc._website = get_string(ai);
337 } else if (ai->id() == "supportsMzn") {
338 sc._supportsMzn = get_bool(ai);
339 } else if (ai->id() == "supportsFzn") {
340 sc._supportsFzn = get_bool(ai);
341 } else if (ai->id() == "supportsNL") {
342 sc._supportsNL = get_bool(ai);
343 } else if (ai->id() == "needsSolns2Out") {
344 sc._needsSolns2Out = get_bool(ai);
345 } else if (ai->id() == "isGUIApplication") {
346 sc._isGUIApplication = get_bool(ai);
347 } else if (ai->id() == "needsMznExecutable") {
348 sc._needsMznExecutable = get_bool(ai);
349 } else if (ai->id() == "needsStdlibDir") {
350 sc._needsStdlibDir = get_bool(ai);
351 } else if (ai->id() == "needsPathsFile") {
352 sc._needsPathsFile = get_bool(ai);
353 } else if (ai->id() == "tags") {
354 sc._tags = get_string_list(ai);
355 } else if (ai->id() == "stdFlags") {
356 sc._stdFlags = get_string_list(ai);
357 } else if (ai->id() == "requiredFlags") {
358 sc._requiredFlags = get_string_list(ai);
359 } else if (ai->id() == "extraFlags") {
360 sc._extraFlags = get_extra_flag_list(ai);
361 } else {
362 std::ostringstream ss;
363 ss << "invalid configuration item (" << ai->id() << ")";
364 throw ConfigException(ss.str());
365 }
366 } else {
367 throw ConfigException("invalid configuration item");
368 }
369 }
370 if (!hadId) {
371 throw ConfigException("invalid solver configuration (missing id)");
372 }
373 if (!hadVersion) {
374 throw ConfigException("invalid solver configuration (missing version)");
375 }
376 if (!hadName) {
377 throw ConfigException("invalid solver configuration (missing name)");
378 }
379 } else {
380 throw ConfigException(errstream.str());
381 }
382 } catch (ConfigException&) {
383 throw;
384 } catch (Exception& e) {
385 throw ConfigException(e.what());
386 }
387
388 return sc;
389 }
390
toJSON(const SolverConfigs & configs) const391 std::string SolverConfig::toJSON(const SolverConfigs& configs) const {
392 GCLock lock;
393 std::ostringstream oss;
394 auto def_id = configs.defaultSolver("");
395 oss << "{\n";
396 oss << " \"extraInfo\": {\n";
397 if (!def_id.empty() && def_id == id()) {
398 oss << " \"isDefault\": true,\n";
399 }
400 if (!mznlibResolved().empty()) {
401 oss << " \"mznlib\": \"" << Printer::escapeStringLit(mznlibResolved()) << "\",\n";
402 }
403 if (!executableResolved().empty()) {
404 oss << " \"executable\": \"" << Printer::escapeStringLit(executableResolved()) << "\",\n";
405 }
406 oss << " \"configFile\": \"" << Printer::escapeStringLit(configFile()) << "\"";
407 if (!defaultFlags().empty()) {
408 oss << ",\n \"defaultFlags\": [";
409 for (unsigned int j = 0; j < defaultFlags().size(); j++) {
410 oss << "\"" << Printer::escapeStringLit(defaultFlags()[j]) << "\"";
411 if (j < defaultFlags().size() - 1) {
412 oss << ",";
413 }
414 }
415 oss << "]";
416 }
417 oss << "\n";
418 oss << " },\n";
419 oss << " \"id\": \"" << Printer::escapeStringLit(id()) << "\",\n";
420 oss << " \"name\": \"" << Printer::escapeStringLit(name()) << "\",\n";
421 oss << " \"version\": \"" << Printer::escapeStringLit(version()) << "\",\n";
422 if (!mznlib().empty()) {
423 oss << " \"mznlib\": \"" << Printer::escapeStringLit(mznlib()) << "\",\n";
424 }
425 if (!executable().empty()) {
426 oss << " \"executable\": \"" << Printer::escapeStringLit(executable()) << "\",\n";
427 }
428 oss << " \"mznlibVersion\": " << mznlibVersion() << ",\n";
429 if (!description().empty()) {
430 oss << " \"description\": \"" << Printer::escapeStringLit(description()) << "\",\n";
431 }
432 if (!contact().empty()) {
433 oss << " \"contact\": \"" << Printer::escapeStringLit(contact()) << "\",\n";
434 }
435 if (!website().empty()) {
436 oss << " \"website\": \"" << Printer::escapeStringLit(website()) << "\",\n";
437 }
438 if (!requiredFlags().empty()) {
439 oss << " \"requiredFlags\": [";
440 for (unsigned int j = 0; j < requiredFlags().size(); j++) {
441 oss << "\"" << requiredFlags()[j] << "\"";
442 if (j < requiredFlags().size() - 1) {
443 oss << ",";
444 }
445 }
446 oss << "],\n";
447 }
448 if (!stdFlags().empty()) {
449 oss << " \"stdFlags\": [";
450 for (unsigned int j = 0; j < stdFlags().size(); j++) {
451 oss << "\"" << stdFlags()[j] << "\"";
452 if (j < stdFlags().size() - 1) {
453 oss << ",";
454 }
455 }
456 oss << "],\n";
457 }
458 if (!extraFlags().empty()) {
459 oss << " \"extraFlags\": [";
460 for (unsigned int j = 0; j < extraFlags().size(); j++) {
461 oss << "\n ["
462 << "\"" << Printer::escapeStringLit(extraFlags()[j].flag) << "\",\""
463 << Printer::escapeStringLit(extraFlags()[j].description) << "\",\"";
464 switch (extraFlags()[j].flagType) {
465 case ExtraFlag::FlagType::T_BOOL:
466 oss << "bool";
467 break;
468 case ExtraFlag::FlagType::T_INT:
469 oss << "int";
470 break;
471 case ExtraFlag::FlagType::T_FLOAT:
472 oss << "float";
473 break;
474 case ExtraFlag::FlagType::T_STRING:
475 oss << (extraFlags()[j].range.empty() ? "string" : "opt");
476 break;
477 }
478 for (const auto& v : extraFlags()[j].range) {
479 oss << ":" << Printer::escapeStringLit(v);
480 }
481 oss << "\",\"" << Printer::escapeStringLit(extraFlags()[j].defaultValue) << "\"]";
482 if (j < extraFlags().size() - 1) {
483 oss << ",";
484 }
485 }
486 oss << "\n ],\n";
487 }
488
489 if (!tags().empty()) {
490 oss << " \"tags\": [";
491 for (unsigned int j = 0; j < tags().size(); j++) {
492 oss << "\"" << Printer::escapeStringLit(tags()[j]) << "\"";
493 if (j < tags().size() - 1) {
494 oss << ",";
495 }
496 }
497 oss << "],\n";
498 }
499 oss << " \"supportsMzn\": " << (supportsMzn() ? "true" : "false") << ",\n";
500 oss << " \"supportsFzn\": " << (supportsFzn() ? "true" : "false") << ",\n";
501 oss << " \"supportsNL\": " << (supportsNL() ? "true" : "false") << ",\n";
502 oss << " \"needsSolns2Out\": " << (needsSolns2Out() ? "true" : "false") << ",\n";
503 oss << " \"needsMznExecutable\": " << (needsMznExecutable() ? "true" : "false") << ",\n";
504 oss << " \"needsStdlibDir\": " << (needsStdlibDir() ? "true" : "false") << ",\n";
505 oss << " \"needsPathsFile\": " << (needsPathsFile() ? "true" : "false") << ",\n";
506 oss << " \"isGUIApplication\": " << (isGUIApplication() ? "true" : "false") << "\n";
507 oss << "}";
508
509 return oss.str();
510 }
511
512 class BuiltinSolverConfigs {
513 public:
514 std::unordered_map<std::string, SolverConfig> builtinSolvers;
515 };
516
builtin_solver_configs()517 BuiltinSolverConfigs& builtin_solver_configs() {
518 static BuiltinSolverConfigs c;
519 return c;
520 }
521
addConfig(const MiniZinc::SolverConfig & sc)522 void SolverConfigs::addConfig(const MiniZinc::SolverConfig& sc) {
523 int newIdx = static_cast<int>(_solvers.size());
524 _solvers.push_back(sc);
525 std::vector<string> sc_tags = sc.tags();
526 std::string id = string_to_lower(sc.id());
527 std::string name = string_to_lower(sc.name());
528 sc_tags.push_back(id);
529 size_t last_dot = id.find_last_of('.');
530 if (last_dot != std::string::npos) {
531 std::string last_id = id.substr(last_dot + 1);
532 if (last_id != name) {
533 sc_tags.push_back(last_id);
534 }
535 }
536 sc_tags.push_back(name);
537 for (const auto& t : sc_tags) {
538 auto it = _tags.find(t);
539 if (it == _tags.end()) {
540 _tags.insert(std::make_pair(t, std::vector<int>({newIdx})));
541 } else {
542 it->second.push_back(newIdx);
543 }
544 }
545 }
546
solverConfigsPath() const547 std::vector<std::string> SolverConfigs::solverConfigsPath() const { return _solverPath; }
548
SolverConfigs(std::ostream & log)549 SolverConfigs::SolverConfigs(std::ostream& log) {
550 #ifdef _MSC_VER
551 const char* PATHSEP = ";";
552 #else
553 const char* PATHSEP = ":";
554 #endif
555 std::string mzn_solver_path = get_env("MZN_SOLVER_PATH");
556 while (!mzn_solver_path.empty()) {
557 size_t next_sep = mzn_solver_path.find(PATHSEP);
558 string cur_path = mzn_solver_path.substr(0, next_sep);
559 _solverPath.push_back(cur_path);
560 if (next_sep != string::npos) {
561 mzn_solver_path = mzn_solver_path.substr(next_sep + 1, string::npos);
562 } else {
563 mzn_solver_path = "";
564 }
565 }
566 std::string userConfigDir = FileUtils::user_config_dir();
567 if (FileUtils::directory_exists(userConfigDir + "/solvers")) {
568 _solverPath.push_back(userConfigDir + "/solvers");
569 }
570 std::vector<std::string> configFiles(
571 {FileUtils::global_config_file(), FileUtils::user_config_file()});
572
573 for (auto& cf : configFiles) {
574 if (!cf.empty() && FileUtils::file_exists(cf)) {
575 ostringstream errstream;
576 try {
577 Env userconfenv;
578 Model* m = nullptr;
579 if (JSONParser::fileIsJSON(cf)) {
580 JSONParser jp(userconfenv.envi());
581 try {
582 m = new Model;
583 GCLock lock;
584 jp.parse(m, cf, false);
585 } catch (JSONError&) {
586 delete m;
587 m = nullptr;
588 }
589 }
590 if (m != nullptr) {
591 for (auto& i : *m) {
592 if (auto* ai = i->dynamicCast<AssignI>()) {
593 if (ai->id() == "mzn_solver_path") {
594 std::vector<std::string> sp = get_string_list(ai);
595 for (const auto& s : sp) {
596 _solverPath.push_back(s);
597 }
598 } else if (ai->id() == "mzn_lib_dir") {
599 _mznlibDir = get_string(ai);
600 } else if (ai->id() == "tagDefaults") {
601 std::vector<std::pair<std::string, std::string> > tagDefs =
602 get_string_pair_list(ai);
603 for (auto& td : tagDefs) {
604 std::string tag = td.first;
605 std::string solver_id = td.second;
606 _tagDefault[tag] = solver_id;
607 }
608 } else if (ai->id() == "solverDefaults") {
609 std::vector<std::vector<std::string> > solverDefs = get_default_option_list(ai);
610 for (auto& sd : solverDefs) {
611 assert(sd.size() == 3);
612 std::string solver = sd[0];
613 auto it = _solverDefaultOptions.find(solver);
614 if (it == _solverDefaultOptions.end()) {
615 std::vector<std::string> solverOptions({sd[1], sd[2]});
616 _solverDefaultOptions.insert(std::make_pair(solver, solverOptions));
617 } else {
618 std::vector<std::string>& opts = it->second;
619 bool found = false;
620 for (unsigned int i = 0; i < opts.size(); i += 2) {
621 if (opts[i] == sd[1]) {
622 // Override existing option value
623 opts[i + 1] = sd[2];
624 found = true;
625 break;
626 }
627 }
628 if (!found) {
629 // Option didn't exist, add to end of list
630 opts.push_back(sd[1]);
631 opts.push_back(sd[2]);
632 }
633 }
634 }
635 } else {
636 throw ConfigException("invalid configuration item");
637 }
638 } else {
639 throw ConfigException("invalid configuration item");
640 }
641 }
642 } else {
643 std::cerr << errstream.str();
644 throw ConfigException("internal error");
645 }
646 } catch (ConfigException& e) {
647 log << "Warning: invalid configuration file: " << e.msg() << "\n";
648 } catch (Exception& e) {
649 log << "Warning: invalid configuration file: " << e.what() << "\n";
650 }
651 }
652 }
653
654 if (_mznlibDir.empty()) {
655 _mznlibDir = FileUtils::file_path(FileUtils::share_directory());
656 }
657 if (!_mznlibDir.empty()) {
658 _solverPath.push_back(_mznlibDir + "/solvers");
659 }
660 #ifndef _MSC_VER
661 if (_mznlibDir != "/usr/local/share/minizinc" &&
662 FileUtils::directory_exists("/usr/local/share")) {
663 _solverPath.emplace_back("/usr/local/share/minizinc/solvers");
664 }
665 # if !defined(__FreeBSD__)
666 if (_mznlibDir != "/usr/share/minizinc" && FileUtils::directory_exists("/usr/share")) {
667 _solverPath.emplace_back("/usr/share/minizinc/solvers");
668 }
669 # endif
670 #endif
671 }
672
populate(std::ostream & log)673 void SolverConfigs::populate(std::ostream& log) {
674 for (const auto& sc : builtin_solver_configs().builtinSolvers) {
675 addConfig(sc.second);
676 }
677
678 for (const string& cur_path : _solverPath) {
679 std::vector<std::string> configFiles = FileUtils::directory_list(cur_path, "msc");
680 for (auto& configFile : configFiles) {
681 try {
682 SolverConfig sc = SolverConfig::load(cur_path + "/" + configFile);
683 addConfig(sc);
684 } catch (ConfigException& e) {
685 log << "Warning: error loading solver configuration from file " << cur_path << "/"
686 << configFile << "\n";
687 log << "Error was:\n" << e.msg() << "\n";
688 }
689 }
690 }
691
692 // Add default options to loaded solver configurations
693 for (auto& sc : _solvers) {
694 sc.defaultFlags(defaultOptions(sc.id()));
695 }
696 }
697
solvers() const698 vector<string> SolverConfigs::solvers() const {
699 // Find default solver, if present
700 std::string def_id;
701 auto def_it = _tagDefault.find("");
702 if (def_it != _tagDefault.end()) {
703 def_id = def_it->second;
704 }
705 // Create sorted list of solvers
706 vector<string> s;
707 for (const auto& sc : _solvers) {
708 if (std::find(sc.tags().begin(), sc.tags().end(), "__internal__") != sc.tags().end()) {
709 continue;
710 }
711 std::ostringstream oss;
712 oss << sc.name() << " " << sc.version() << " (" << sc.id();
713 if (!def_id.empty() && sc.id() == def_id) {
714 oss << ", default solver";
715 }
716 for (const std::string& t : sc.tags()) {
717 oss << ", " << t;
718 }
719 oss << ")";
720 s.push_back(oss.str());
721 }
722 SortByLowercase sortByLowercase;
723 std::sort(s.begin(), s.end(), sortByLowercase);
724 return s;
725 }
726
solverConfigsJSON() const727 std::string SolverConfigs::solverConfigsJSON() const {
728 std::ostringstream oss;
729
730 SortByName sortByName(_solvers);
731 std::vector<int> solversIdx(_solvers.size());
732 for (unsigned int i = 0; i < solversIdx.size(); i++) {
733 solversIdx[i] = i;
734 }
735 std::sort(solversIdx.begin(), solversIdx.end(), sortByName);
736
737 bool hadSolver = false;
738 oss << "[";
739 for (unsigned int i = 0; i < _solvers.size(); i++) {
740 const SolverConfig& sc = _solvers[solversIdx[i]];
741 if (std::find(sc.tags().begin(), sc.tags().end(), "__internal__") != sc.tags().end()) {
742 continue;
743 }
744 if (hadSolver) {
745 oss << ",";
746 }
747 hadSolver = true;
748 std::istringstream iss(sc.toJSON(*this));
749 std::string line;
750 while (std::getline(iss, line)) {
751 oss << "\n " << line;
752 }
753 }
754 oss << "\n]\n";
755 return oss.str();
756 }
757
758 namespace {
get_tag(const std::string & t)759 std::string get_tag(const std::string& t) { return t.substr(0, t.find('@')); }
get_version(const std::string & t)760 std::string get_version(const std::string& t) {
761 size_t sep = t.find('@');
762 return sep == string::npos ? "" : t.substr(sep + 1);
763 }
764 } // namespace
765
config(const std::string & _s)766 const SolverConfig& SolverConfigs::config(const std::string& _s) {
767 std::string s;
768 if (_s.size() > 4 && _s.substr(_s.size() - 4) == ".msc") {
769 SolverConfig sc = SolverConfig::load(_s);
770 addConfig(sc);
771 s = sc.id() + "@" + sc.version();
772 } else {
773 s = _s;
774 }
775 s = string_to_lower(s);
776 std::vector<std::string> tags;
777 std::istringstream iss(s);
778 std::string next_s;
779 while (std::getline(iss, next_s, ',')) {
780 tags.push_back(next_s);
781 }
782 std::set<std::string> defaultSolvers;
783 std::set<int> selectedSolvers;
784
785 std::string firstTag;
786 if (tags.empty()) {
787 DefaultMap::const_iterator def_it = _tagDefault.find("");
788 if (def_it != _tagDefault.end()) {
789 firstTag = def_it->second;
790 } else {
791 throw ConfigException("no solver selected");
792 }
793 } else {
794 firstTag = tags[0];
795 }
796 TagMap::const_iterator tag_it = _tags.find(get_tag(firstTag));
797
798 if (tag_it == _tags.end()) {
799 throw ConfigException("no solver with tag " + get_tag(firstTag) + " found");
800 }
801 std::string tv = get_version(firstTag);
802 for (int sidx : tag_it->second) {
803 if (tv.empty() || tv == _solvers[sidx].version()) {
804 selectedSolvers.insert(sidx);
805 }
806 }
807 if (selectedSolvers.empty()) {
808 // No matching version, only matching tag
809 for (int sidx : tag_it->second) {
810 selectedSolvers.insert(sidx);
811 }
812 }
813 DefaultMap::const_iterator def_it = _tagDefault.find(get_tag(firstTag));
814 if (def_it != _tagDefault.end()) {
815 defaultSolvers.insert(def_it->second);
816 }
817 for (unsigned int i = 1; i < tags.size(); i++) {
818 tag_it = _tags.find(get_tag(tags[i]));
819 if (tag_it == _tags.end()) {
820 throw ConfigException("no solver with tag " + tags[i] + " found");
821 }
822 tv = get_version(tags[i]);
823 std::set<int> newSolvers;
824 for (int sidx : tag_it->second) {
825 if (tv.empty() || tv == _solvers[sidx].version()) {
826 newSolvers.insert(sidx);
827 }
828 }
829 std::set<int> intersection;
830 std::set_intersection(selectedSolvers.begin(), selectedSolvers.end(), newSolvers.begin(),
831 newSolvers.end(), std::inserter(intersection, intersection.begin()));
832 selectedSolvers = intersection;
833 if (selectedSolvers.empty()) {
834 throw ConfigException("no solver with tags " + s + " found");
835 }
836 def_it = _tagDefault.find(get_tag(tags[i]));
837 if (def_it != _tagDefault.end()) {
838 defaultSolvers.insert(def_it->second);
839 }
840 }
841 int selectedSolver = -1;
842 if (selectedSolvers.size() > 1) {
843 // use default information for the tags to select a solver
844 for (int sc_idx : selectedSolvers) {
845 if (defaultSolvers.find(_solvers[sc_idx].id()) != defaultSolvers.end()) {
846 selectedSolver = sc_idx;
847 break;
848 }
849 }
850 if (selectedSolver == -1) {
851 selectedSolver = *selectedSolvers.begin();
852 }
853 } else {
854 selectedSolver = *selectedSolvers.begin();
855 }
856 return _solvers[selectedSolver];
857 }
858
defaultOptions(const std::string & id)859 std::vector<std::string> SolverConfigs::defaultOptions(const std::string& id) {
860 auto it = _solverDefaultOptions.find(id);
861 if (it != _solverDefaultOptions.end()) {
862 std::vector<std::string> defaultOptions;
863 for (const auto& df : it->second) {
864 if (!df.empty()) {
865 defaultOptions.push_back(df);
866 }
867 }
868 return defaultOptions;
869 }
870 return {};
871 }
872
registerBuiltinSolver(const SolverConfig & sc)873 void SolverConfigs::registerBuiltinSolver(const SolverConfig& sc) {
874 builtin_solver_configs().builtinSolvers.insert(make_pair(sc.id(), sc));
875 }
876
877 } // namespace MiniZinc
878