From ff64a2b4cc1531cc4a7da3f741bcc4cb65caacdb Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Wed, 8 Jul 2026 10:07:13 +0200 Subject: [PATCH 1/8] Check generated changelog output --- .github/workflows/build.yml | 15 ++ changelog.in | 52 +++--- misc/genchangelog.py | 54 +++---- misc/tidy.py | 311 ++++++++++++++++++++++++++++++++++++ 4 files changed, 379 insertions(+), 53 deletions(-) create mode 100644 misc/tidy.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 14eaf6e57a..8360349c48 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,21 @@ env: BUILD_TYPE: Release jobs: + tidy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + + - name: Install uv + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 + with: + enable-cache: false + + - name: Tidy checks + shell: bash + run: uv run misc/tidy.py --check + build-autoconf: runs-on: ${{ matrix.os }} strategy: diff --git a/changelog.in b/changelog.in index 3b76bb1a95..0e2d79b524 100755 --- a/changelog.in +++ b/changelog.in @@ -887,7 +887,7 @@ exp(0.0) now correctly evaluates to 1.0 (and not 0.0). [ENTRY] Module: int -What: changed +What: change Rank: minor Thanks: Jens Krueger [DESCRIPTION] @@ -1031,6 +1031,21 @@ Rank: minor Added missing definitions for declarations of creating Home objects from spaces with propagator and brancher groups. +[ENTRY] +Module: kernel +What: new +Rank: minor +Thanks: Gilles Pesant and Samuel Gagnon +[DESCRIPTION] +Introduction of a basic structure for supporting counting-based +search as described in: +S. Gagnon, G. Pesant, Accelerating Counting-Based Search, CPAIOR 2018. +This is only enabled if Gecode has been compiled with the option +--enable-cbs. +[MORE] +A branching option will be added to compatible examples as +counting algorithms are added to constraints. + [ENTRY] Module: int What: new @@ -1844,7 +1859,7 @@ Tracing now also records failure-events, see MPG for details. [ENTRY] Module: int -What: changed +What: change Rank: minor Thanks: Conrad Drescher [DESCRIPTION] @@ -1941,7 +1956,7 @@ heuristic for branching. See MPG for details. [ENTRY] Module: kernel -What: changed +What: change Rank: minor [DESCRIPTION] Do not count number of weakly monotonic propagators, nobody used @@ -2032,7 +2047,7 @@ and sequence constraints). [ENTRY] Module: int -What: changed +What: change Rank: major [DESCRIPTION] All branching functions (merit, value, commit, tie-breaking @@ -2040,7 +2055,7 @@ limit, filter, print) can now be of std::function type. [ENTRY] Module: set -What: changed +What: change Rank: major [DESCRIPTION] All branching functions (merit, value, commit, tie-breaking @@ -2048,7 +2063,7 @@ limit, filter, print) can now be of std::function type. [ENTRY] Module: float -What: changed +What: change Rank: major [DESCRIPTION] All branching functions (merit, value, commit, tie-breaking @@ -2056,7 +2071,7 @@ limit, filter, print) can now be of std::function type. [ENTRY] Module: kernel -What: changed +What: change Rank: major [DESCRIPTION] All branching functions (merit, value, commit, tie-breaking @@ -6634,8 +6649,8 @@ Sudoku and GraphColor now uses smallest size over accumulated failure count (AFC) as the default heuristic. [ENTRY] -Module: bug -What: set +Module: set +What: bug Rank: minor [DESCRIPTION] For branching, SET_VAR_DEGREE_MAX would select the variable with @@ -8383,7 +8398,7 @@ Rank: minor Fixed bug in unreflection of empty VarArrays. [ENTRY] -Module: linear +Module: int What: bug Rank: major [DESCRIPTION] @@ -8550,7 +8565,7 @@ The number of propagators and branchings can be accurately retrieved from both failed and non-stable spaces. [ENTRY] -Module: examples +Module: example What: new Rank: minor [DESCRIPTION] @@ -10613,18 +10628,3 @@ Version: 1.0.0 Date: 2005-12-06 [DESCRIPTION] Initial release. - -[ENTRY] -Module: kernel -What: new -Rank: minor -Thanks: Gilles Pesant and Samuel Gagnon -[DESCRIPTION] -Introduction of a basic structure for supporting counting-based -search as described in: -S. Gagnon, G. Pesant, Accelerating Counting-Based Search, CPAIOR 2018. -This is only enabled if Gecode has been compiled with the option ---enable-cbs. -[MORE] -A branching option will be added to compatible examples as -counting algorithms are added to constraints. diff --git a/misc/genchangelog.py b/misc/genchangelog.py index 2990ec4614..9bf2db440f 100644 --- a/misc/genchangelog.py +++ b/misc/genchangelog.py @@ -80,6 +80,27 @@ def main() -> int: versions = "" body = "" + def flush_current_release() -> None: + nonlocal versions, body + + sid = f"SectionChanges_{version}" + sid = sid.replace(".", "_") + versions = versions + f'\n - \\ref {sid} "Gecode {version} ({date})"' + body = body + f"\\section {sid} Changes in Version {version} ({date})\n\n" + body = body + f"{info}\n\n" + + for mod in modorder: + if hastext[mod]: + body = body + " - " + modclear[mod] + "\n" + hastext[mod] = 0 + for what in whatorder: + k = f"{mod}-{what}" + if text[k] != "": + body = body + " - " + whatclear[what] + "\n" + body = body + text[k] + text[k] = "" + body = body + "\n\n" + lines = sys.stdin.readlines() idx = 0 current = None @@ -99,23 +120,7 @@ def main() -> int: if re.match(r"^\[RELEASE\]", l): if not first: - sid = f"SectionChanges_{version}" - sid = sid.replace(".", "_") - versions = versions + f'\n - \\ref {sid} "Gecode {version} ({date})"' - body = body + f"\\section {sid} Changes in Version {version} ({date})\n\n" - body = body + f"{info}\n\n" - - for mod in modorder: - if hastext[mod]: - body = body + " - " + modclear[mod] + "\n" - hastext[mod] = 0 - for what in whatorder: - k = f"{mod}-{what}" - if text[k] != "": - body = body + " - " + whatclear[what] + "\n" - body = body + text[k] - text[k] = "" - body = body + "\n\n" + flush_current_release() first = False version = "" @@ -206,12 +211,13 @@ def main() -> int: more = trim_trailing_ws(more) more = more.replace("<", "\\<") more = more.replace(">", "\\>") - more = more.replace("<", "\\<") more = more.replace("&", "\\&") more = re.sub(r"\n([ \t]*)\n", r"\n\1
\n", more) hastext[mod] = 1 rb = rankclear.get(rank, "") + if bug != "": + rb = rb + f", bugzilla entry {bug}" if issue != "": rb = rb + f', issue {issue}' if thanks != "": @@ -237,6 +243,9 @@ def main() -> int: current = l continue + if not first: + flush_current_release() + sys.stdout.write( f"""/** @@ -245,7 +254,6 @@ def main() -> int: \\section SectionChangeList Changes in Gecode Versions {versions} - - \\ref SectionChanges_1_0_0 "Gecode 1.0.0 (2005-12-06)" \\section SectionChangeWhat Gecode Version Numbers @@ -263,14 +271,6 @@ def main() -> int: ) sys.stdout.write(body) - sys.stdout.write( - """\\section SectionChanges_1_0_0 Changes in Version 1.0.0 (2005-12-06, initial release) - -No changes, of course. - - -""" - ) sys.stdout.write("\n*/\n") return 0 diff --git a/misc/tidy.py b/misc/tidy.py new file mode 100644 index 0000000000..aec0ef0adc --- /dev/null +++ b/misc/tidy.py @@ -0,0 +1,311 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.11" +# /// + +"""Run repository formatting and generated-file consistency checks.""" + +from __future__ import annotations + +import argparse +import difflib +import re +import subprocess +import sys +from dataclasses import dataclass +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +CHANGELOG_IN = ROOT / "changelog.in" +CHANGELOG_HH = ROOT / "changelog.hh" +GEN_CHANGELOG = ROOT / "misc" / "genchangelog.py" + +MODULES = { + "kernel", + "search", + "int", + "set", + "float", + "cpltset", + "minimodel", + "graph", + "scheduling", + "driver", + "iter", + "support", + "example", + "test", + "gist", + "flatzinc", + "other", +} +WHATS = {"bug", "performance", "documentation", "new", "removed", "change"} +RANKS = {"minor", "major"} + + +@dataclass +class Release: + line: int + version: str + date: str + + +@dataclass +class Entry: + line: int + release: Release + module: str + what: str + rank: str + bug: str + issue: str + thanks: str + author: str + description: str + more: str + + +def run(cmd: list[str], *, stdin: str | None = None) -> subprocess.CompletedProcess[str]: + return subprocess.run( + cmd, + cwd=ROOT, + input=stdin, + text=True, + capture_output=True, + check=False, + ) + + +def generate_changelog() -> str: + source = CHANGELOG_IN.read_text() + result = run(["uv", "run", str(GEN_CHANGELOG)], stdin=source) + if result.returncode != 0: + sys.stderr.write(result.stdout) + sys.stderr.write(result.stderr) + raise SystemExit(result.returncode) + return result.stdout + + +def is_tracked(path: Path) -> bool: + rel = str(path.relative_to(ROOT)) + return run(["git", "ls-files", "--error-unmatch", rel]).returncode == 0 + + +def parse_changelog() -> tuple[list[Release], list[Entry]]: + lines = CHANGELOG_IN.read_text().splitlines(keepends=True) + releases: list[Release] = [] + entries: list[Entry] = [] + current_release: Release | None = None + idx = 0 + + while idx < len(lines): + line = lines[idx] + if line.startswith("#") or not line.strip(): + idx += 1 + continue + + if line.startswith("[RELEASE]"): + start = idx + 1 + idx += 1 + version = "" + date = "" + while idx < len(lines): + current = lines[idx] + idx += 1 + match = re.search(r"Version:[\t ]*(.*)$", current) + if match: + version = match.group(1).rstrip("\n") + match = re.search(r"Date:[\t ]*(.*)$", current) + if match: + date = match.group(1).rstrip("\n") + if "[DESCRIPTION]" in current: + break + while idx < len(lines) and "[ENTRY]" not in lines[idx]: + idx += 1 + current_release = Release(start, version, date) + releases.append(current_release) + continue + + if line.startswith("[ENTRY]"): + if current_release is None: + raise ValueError(f"entry at line {idx + 1} has no preceding release") + + start = idx + 1 + idx += 1 + metadata = { + "module": "", + "what": "", + "rank": "", + "bug": "", + "issue": "", + "thanks": "", + "author": "", + } + fields = { + "module": "Module", + "what": "What", + "rank": "Rank", + "bug": "Bug", + "issue": "Issue", + "thanks": "Thanks", + "author": "Author", + } + while idx < len(lines): + current = lines[idx] + idx += 1 + if "[DESCRIPTION]" in current: + break + for key, field in fields.items(): + match = re.search(rf"{field}:[\t ]*(.*)$", current) + if match: + metadata[key] = match.group(1).rstrip("\n") + + description: list[str] = [] + more: list[str] = [] + in_more = False + while idx < len(lines) and "[ENTRY]" not in lines[idx] and "[RELEASE]" not in lines[idx]: + current = lines[idx] + idx += 1 + if "[MORE]" in current: + in_more = True + continue + (more if in_more else description).append(current) + + entries.append( + Entry( + line=start, + release=current_release, + description="".join(description), + more="".join(more), + **metadata, + ) + ) + continue + + idx += 1 + + return releases, entries + + +def changelog_text(raw: str) -> str: + text = "".join(" " + line for line in raw.splitlines(keepends=True)) + text = re.sub(r"^ ", "", text) + text = re.sub(r"[ \t\n\r]*$", "", text) + text = text.replace("<", "\\<") + text = text.replace(">", "\\>") + text = text.replace("&", "\\&") + return re.sub(r"\n([ \t]*)\n", r"\n\1
\n", text) + + +def generated_section(generated: str, version: str) -> str: + section_id = version.replace(".", "_") + marker = f"\\section SectionChanges_{section_id} " + start = generated.find(marker) + if start < 0: + return "" + end = generated.find("\n\\section SectionChanges_", start + 1) + return generated[start:] if end < 0 else generated[start:end] + + +def validate_changelog_entries(generated: str) -> list[str]: + releases, entries = parse_changelog() + errors: list[str] = [] + + for release in releases: + section_id = release.version.replace(".", "_") + heading = ( + f"\\section SectionChanges_{section_id} " + f"Changes in Version {release.version} ({release.date})" + ) + if heading not in generated: + errors.append(f"line {release.line}: missing generated section for {release.version}") + + for entry in entries: + if entry.module not in MODULES: + errors.append(f"line {entry.line}: unknown module {entry.module!r}") + if entry.what not in WHATS: + errors.append(f"line {entry.line}: unknown change type {entry.what!r}") + if entry.rank not in RANKS: + errors.append(f"line {entry.line}: unknown rank {entry.rank!r}") + + if errors: + return errors + + for entry in entries: + section = generated_section(generated, entry.release.version) + description = changelog_text(entry.description) + position = section.find(description) + if position < 0: + errors.append( + f"line {entry.line}: description missing from generated section " + f"{entry.release.version}" + ) + continue + + tail = section[position : position + len(description) + 800] + expected = [entry.rank] + if entry.bug: + expected.append(f"bugzilla entry {entry.bug}") + if entry.issue: + expected.append(f"issue {entry.issue}") + if entry.thanks: + expected.append(f"thanks to {entry.thanks}") + if entry.author: + expected.append(f"contributed by {entry.author}") + for item in expected: + if item not in tail: + errors.append(f"line {entry.line}: missing generated metadata {item!r}") + + if entry.more and changelog_text(entry.more) not in section: + errors.append(f"line {entry.line}: [MORE] text missing from generated section") + + return errors + + +def check_changelog(*, fix: bool) -> int: + generated = generate_changelog() + errors = validate_changelog_entries(generated) + + if is_tracked(CHANGELOG_HH): + if CHANGELOG_HH.exists() and CHANGELOG_HH.read_text() == generated: + pass + elif fix: + CHANGELOG_HH.write_text(generated) + else: + current = CHANGELOG_HH.read_text() if CHANGELOG_HH.exists() else "" + diff = difflib.unified_diff( + current.splitlines(keepends=True), + generated.splitlines(keepends=True), + fromfile="changelog.hh", + tofile="generated changelog.hh", + ) + errors.append("changelog.hh is not up to date with changelog.in") + errors.extend(" " + line.rstrip("\n") for line in diff) + elif fix: + CHANGELOG_HH.write_text(generated) + + if errors: + print("Changelog tidy check failed:", file=sys.stderr) + for error in errors: + print(f"- {error}", file=sys.stderr) + return 1 + + print("Changelog tidy check passed") + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--check", action="store_true", help="check without modifying tracked files") + parser.add_argument("--fix", action="store_true", help="rewrite generated files where applicable") + args = parser.parse_args() + + if args.check and args.fix: + parser.error("--check and --fix are mutually exclusive") + + return check_changelog(fix=args.fix) + + +if __name__ == "__main__": + raise SystemExit(main()) From c7ce1a7a01683a59b6ad84e358e52071e21d3a73 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Fri, 10 Jul 2026 10:02:08 +0200 Subject: [PATCH 2/8] Fix float arithmetic edge cases Certify even-root bounds with direct downward rounding, complete Pow zero and fixpoint propagation, and restore sound Mult contraction at zero endpoints. Add focused regression coverage for each case. --- gecode/float/arithmetic/mult.hpp | 28 ++ gecode/float/arithmetic/pow-nroot.hpp | 107 +++++- test/float/arithmetic.cpp | 470 ++++++++++++++++++++++++++ 3 files changed, 599 insertions(+), 6 deletions(-) diff --git a/gecode/float/arithmetic/mult.hpp b/gecode/float/arithmetic/mult.hpp index 6194e09956..8018e377f5 100755 --- a/gecode/float/arithmetic/mult.hpp +++ b/gecode/float/arithmetic/mult.hpp @@ -196,14 +196,38 @@ namespace Gecode { namespace Float { namespace Arithmetic { return new (home) Mult(home,*this); } + /// Contract a quotient when the denominator has zero as one endpoint + template + forceinline ModEvent + div_zero_endpoint(Home home, View x, const FloatVal& n, const View& d) { + if ((d.min() == 0.0) && (d.max() > 0.0)) { + const FloatVal q = n / FloatVal(d.max()); + if (n.min() > 0.0) + return x.gq(home,q.min()); + if (n.max() < 0.0) + return x.lq(home,q.max()); + } else if ((d.max() == 0.0) && (d.min() < 0.0)) { + const FloatVal q = n / FloatVal(d.min()); + if (n.min() > 0.0) + return x.lq(home,q.max()); + if (n.max() < 0.0) + return x.gq(home,q.min()); + } + return ME_FLOAT_NONE; + } + template ExecStatus Mult::propagate(Space& home, const ModEventDelta&) { GECODE_ME_CHECK(x2.eq(home,x0.val()*x1.val())); if (!x1.zero_in()) GECODE_ME_CHECK(x0.eq(home,x2.val() / x1.val())); + else + GECODE_ME_CHECK(div_zero_endpoint(home,x0,x2.val(),x1)); if (!x0.zero_in()) GECODE_ME_CHECK(x1.eq(home,x2.val() / x0.val())); + else + GECODE_ME_CHECK(div_zero_endpoint(home,x1,x2.val(),x0)); GECODE_ME_CHECK(x2.eq(home,x0.val()*x1.val())); if (x0.assigned() && x1.assigned() && x2.assigned()) return home.ES_SUBSUMED(*this); @@ -222,8 +246,12 @@ namespace Gecode { namespace Float { namespace Arithmetic { GECODE_ME_CHECK(x2.eq(home,x0.val()*x1.val())); if (!x1.zero_in()) GECODE_ME_CHECK(x0.eq(home,x2.val() / x1.val())); + else + GECODE_ME_CHECK(div_zero_endpoint(home,x0,x2.val(),x1)); if (!x0.zero_in()) GECODE_ME_CHECK(x1.eq(home,x2.val() / x0.val())); + else + GECODE_ME_CHECK(div_zero_endpoint(home,x1,x2.val(),x0)); GECODE_ME_CHECK(x2.eq(home,x0.val()*x1.val())); if (x0.assigned() && x1.assigned() && x2.assigned()) return ES_OK; diff --git a/gecode/float/arithmetic/pow-nroot.hpp b/gecode/float/arithmetic/pow-nroot.hpp index daef6ad981..0c4de4aa46 100755 --- a/gecode/float/arithmetic/pow-nroot.hpp +++ b/gecode/float/arithmetic/pow-nroot.hpp @@ -33,12 +33,104 @@ namespace Gecode { namespace Float { namespace Arithmetic { + /// Propagator maintaining that a float view does not contain zero + template + class NonZero : public UnaryPropagator { + protected: + using UnaryPropagator::x0; + /// Constructor for cloning a p + NonZero(Space& home, NonZero& p) + : UnaryPropagator(home,p) {} + /// Constructor for posting + NonZero(Home home, View x) + : UnaryPropagator(home,x) {} + public: + /// Post a non-zero propagator + static ExecStatus post(Home home, View x) { + if ((x.max() < 0.0) || (x.min() > 0.0)) + return ES_OK; + if ((x.min() == 0.0) && (x.max() == 0.0)) + return ES_FAILED; + (void) new (home) NonZero(home,x); + return ES_OK; + } + /// Copy propagator during cloning + virtual Actor* copy(Space& home) { + return new (home) NonZero(home,*this); + } + /// Perform propagation + virtual ExecStatus propagate(Space& home, const ModEventDelta&) { + if ((x0.max() < 0.0) || (x0.min() > 0.0)) + return home.ES_SUBSUMED(*this); + return ((x0.min() == 0.0) && (x0.max() == 0.0)) + ? ES_FAILED : ES_FIX; + } + }; + + /// Return a downward-rounded positive integer power + forceinline FloatNum + positive_pow_down(FloatNum x, int n) { + typedef gecode_boost::numeric::interval_lib::rounded_arith_std + DirectRoundingBase; + typedef gecode_boost::numeric::interval_lib::save_state + DirectRounding; + DirectRounding r; + FloatNum y = 1.0; + while (n > 0) { + if ((n & 1) != 0) + y = r.mul_down(y,x); + n >>= 1; + if (n > 0) + x = r.mul_down(x,x); + } + return y; + } + /// Return nth root for a non-negative interval forceinline FloatVal positive_nroot(const FloatVal& x, int n) { if (((n % 2) == 0) && (x.min() == 0.0)) { + if (x.max() == 0.0) + return FloatVal(0.0,0.0); + + // std::pow is not guaranteed to be correctly rounded. Use it only as + // a seed, and certify the upper bound with outward interval arithmetic. FloatNum u = std::pow(x.max(), 1.0 / static_cast(n)); - return FloatVal(0.0, std::nextafter(u, Limits::max)); + if (!(u > 0.0)) + u = std::nextafter(0.0,Limits::max); + else if (u > Limits::max) + u = Limits::max; + + if (positive_pow_down(u,n) < x.max()) { + FloatNum l = u; + do { + u = (l <= Limits::max / 2.0) ? l * 2.0 : Limits::max; + if (u > Limits::max) + u = Limits::max; + else if (!(u > l)) + u = std::nextafter(l,Limits::max); + if (u == l) + break; + if (positive_pow_down(u,n) < x.max()) + l = u; + else + break; + } while (true); + + // Find the least representable value between the failed lower bound + // and the certified upper bound. This keeps the result tight even if + // the initial approximation was many ulps too small. + while (std::nextafter(l,u) < u) { + FloatNum m = l + (u-l) / 2.0; + if (!(m > l) || !(m < u)) + m = std::nextafter(l,u); + if (positive_pow_down(m,n) < x.max()) + l = m; + else + u = m; + } + } + return FloatVal(0.0,u); } return nroot(x,n); } @@ -58,14 +150,13 @@ namespace Gecode { namespace Float { namespace Arithmetic { ExecStatus Pow::post(Home home, A x0, B x1, int n) { if (n == 0) { - if ((x0.min() == 0.0) && (x0.max() == 0.0)) return ES_FAILED; GECODE_ME_CHECK(x1.eq(home,1.0)); - return ES_OK; + return NonZero::post(home,x0); } GECODE_ME_CHECK(x1.eq(home,pow(x0.domain(),n))); if ((x1.min() == 0.0) && (x1.max() == 0.0)) { - GECODE_ME_CHECK(x1.eq(home,0.0)); + GECODE_ME_CHECK(x0.eq(home,0.0)); return ES_OK; } @@ -88,6 +179,10 @@ namespace Gecode { namespace Float { namespace Arithmetic { GECODE_ME_CHECK(x0.eq(home,nroot(x1.domain(),n))); } + // Inverse propagation can tighten x0. Recompute the forward image before + // an assigned x0 causes the constraint to be discarded. + GECODE_ME_CHECK(x1.eq(home,pow(x0.domain(),n))); + if (!x0.assigned()) (void) new (home) Pow(home,x0,x1,n); return ES_OK; } @@ -106,11 +201,10 @@ namespace Gecode { namespace Float { namespace Arithmetic { template ExecStatus Pow::propagate(Space& home, const ModEventDelta&) { - if ((x0.min() == 0.0) && (x0.max() == 0.0)) return ES_FAILED; GECODE_ME_CHECK(x1.eq(home,pow(x0.domain(),m_n))); if ((x1.min() == 0.0) && (x1.max() == 0.0)) { - GECODE_ME_CHECK(x1.eq(home,0.0)); + GECODE_ME_CHECK(x0.eq(home,0.0)); return home.ES_SUBSUMED(*this); } @@ -132,6 +226,7 @@ namespace Gecode { namespace Float { namespace Arithmetic { } else { GECODE_ME_CHECK(x0.eq(home,nroot(x1.domain(),m_n))); } + GECODE_ME_CHECK(x1.eq(home,pow(x0.domain(),m_n))); return x0.assigned() ? home.ES_SUBSUMED(*this) : ES_NOFIX; } diff --git a/test/float/arithmetic.cpp b/test/float/arithmetic.cpp index 4fba78d9ed..7044a2543a 100755 --- a/test/float/arithmetic.cpp +++ b/test/float/arithmetic.cpp @@ -39,6 +39,8 @@ #include #include +#include +#include namespace Test { namespace Float { @@ -50,6 +52,474 @@ namespace Test { namespace Float { * \ingroup TaskTestFloat */ //@{ + /// Regression test for outward positive even-root bounds + class PositiveNRootBounds : public Base { + protected: + /// Space exercising the Pow and NthRoot propagators + class RootSpace : public Gecode::Space { + public: + /// Variables used by the propagator + Gecode::FloatVar x0, x1; + /// Post an extreme Pow or NthRoot instance + RootSpace(Gecode::FloatNum hi, Gecode::FloatNum witness, int n, + bool usePow) + : x0(*this, 0.0, usePow ? witness : hi), + x1(*this, usePow ? 0.0 : witness, usePow ? hi : witness) { + if (usePow) { + Gecode::pow(*this,x0,n,x1); + Gecode::rel(*this,x0,Gecode::FRT_EQ,witness); + } else { + Gecode::nroot(*this,x0,n,x1); + } + } + /// Clone constructor + RootSpace(RootSpace& s) : Gecode::Space(s) { + x0.update(*this,s.x0); + x1.update(*this,s.x1); + } + /// Copy space during cloning + virtual Gecode::Space* copy(void) { + return new RootSpace(*this); + } + }; + + /// Space exposing the propagated NthRoot upper bound + class NRootBoundSpace : public Gecode::Space { + public: + /// Source and root variables + Gecode::FloatVar source, root; + /// Post an NthRoot constraint with an unconstrained root + NRootBoundSpace(Gecode::FloatNum hi, int n) + : source(*this,0.0,hi), + root(*this,0.0,Gecode::Float::Limits::max) { + Gecode::nroot(*this,source,n,root); + } + /// Clone constructor + NRootBoundSpace(NRootBoundSpace& s) : Gecode::Space(s) { + source.update(*this,s.source); + root.update(*this,s.root); + } + /// Copy space during cloning + virtual Gecode::Space* copy(void) { + return new NRootBoundSpace(*this); + } + }; + + /// Check that a known feasible boundary value is not removed + bool check(Gecode::FloatNum hi, Gecode::FloatNum witness, int n, + int mode, bool usePow) const { + if (std::fesetround(mode) != 0) + return true; + RootSpace s(hi,witness,n,usePow); + if (s.status() != Gecode::SS_FAILED) + return true; + olog << "Root bound removed a solution for " + << (usePow ? "pow" : "nroot") << ", n=" << n + << ", mode=" << mode << std::endl; + return false; + } + /// Check that propagation returns a genuinely outward upper bound + bool checkBound(Gecode::FloatNum hi, Gecode::FloatNum rejected, + int n, int mode) const { + if (std::fesetround(mode) != 0) + return true; + NRootBoundSpace s(hi,n); + if ((s.status() != Gecode::SS_FAILED) && + (s.root.max() > rejected)) + return true; + olog << "NRoot upper bound was not outward for n=" << n + << ", mode=" << mode << std::endl; + return false; + } + public: + /// Create and register test + PositiveNRootBounds(void) + : Base("Float::Arithmetic::PositiveNRootBounds") {} + /// Run test under every supported IEEE-754 rounding mode + virtual bool run(void) { + const int oldMode = std::fegetround(); + const int modes[] = { + FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO + }; + struct Case { + Gecode::FloatNum hi; + Gecode::FloatNum witness; + int n; + } cases[] = { + {std::ldexp(static_cast(0x156e1fc2f8f359ULL), + -1049), + std::ldexp(static_cast(0x14484bfeebc28cULL), + -152), 10}, + {std::numeric_limits::denorm_min(), + std::ldexp(static_cast(0x18406003b2ae42ULL), + -160), 10}, + {std::numeric_limits::max(), + std::ldexp(static_cast(0x1965fea53d6e0fULL), + 118), 6} + }; + bool result = true; + const Gecode::FloatNum roundingHi = + std::ldexp(static_cast(0x1751def03d6f38ULL), + 458); + const Gecode::FloatNum rejectedUpper = + std::ldexp(static_cast(0x1350f4c43079ceULL), + 203); + for (unsigned int m=0; m::denorm_min(), + positive ? std::numeric_limits::denorm_min() + : -0.0), + y(*this,-1.0,2.0) { + Gecode::pow(*this,x,0,y); + } + /// Clone constructor + AdjacentZeroSpace(AdjacentZeroSpace& s) : Gecode::Space(s) { + x.update(*this,s.x); + y.update(*this,s.y); + } + /// Copy space during cloning + virtual Gecode::Space* copy(void) { + return new AdjacentZeroSpace(*this); + } + }; + + /// Space reproducing a Pow inverse-to-forward fixpoint + class FixpointSpace : public Gecode::Space { + public: + /// Base and result variables + Gecode::FloatVar x, y; + /// Post the fixpoint regression instance + FixpointSpace(void) + : x(*this, + -std::ldexp(static_cast + (0x10000000000006ULL),-53), + -std::ldexp(static_cast + (0x10000000000004ULL),-53)), + y(*this, + std::ldexp(static_cast + (0x1000000000000aULL),-54), + std::ldexp(static_cast + (0x1000000000000cULL),-54)) { + Gecode::pow(*this,x,2,y); + } + /// Clone constructor + FixpointSpace(FixpointSpace& s) : Gecode::Space(s) { + x.update(*this,s.x); + y.update(*this,s.y); + } + /// Copy space during cloning + virtual Gecode::Space* copy(void) { + return new FixpointSpace(*this); + } + }; + + /// Space for direct and delayed assignment of the result to zero + class ZeroResultSpace : public Gecode::Space { + public: + /// Base and result variables + Gecode::FloatVar x, y; + /// Post a power constraint with either a zero or a wide result + ZeroResultSpace(int n, bool direct) + : x(*this,-1.0,1.0), + y(*this,direct ? 0.0 : -1.0,direct ? 0.0 : 1.0) { + Gecode::pow(*this,x,n,y); + } + /// Clone constructor + ZeroResultSpace(ZeroResultSpace& s) : Gecode::Space(s) { + x.update(*this,s.x); + y.update(*this,s.y); + } + /// Copy space during cloning + virtual Gecode::Space* copy(void) { + return new ZeroResultSpace(*this); + } + }; + + /// Test one exponent with direct and delayed zero assignment + bool checkZero(int n) const { + ZeroSpace direct(n,true); + ZeroSpace delayed(n,false); + if (delayed.status() == Gecode::SS_FAILED) { + olog << "Pow wide base failed for n=" << n << std::endl; + return false; + } + ZeroSpace* clone = static_cast(delayed.clone()); + Gecode::rel(*clone,clone->x,Gecode::FRT_EQ,0.0); + if (n == 0) { + const bool zeroRejected = + (direct.status() == Gecode::SS_FAILED) && + (clone->status() == Gecode::SS_FAILED); + delete clone; + ZeroSpace* nonZero = static_cast(delayed.clone()); + Gecode::rel(*nonZero,nonZero->x,Gecode::FRT_EQ,0.5); + const bool nonZeroAccepted = + (nonZero->status() != Gecode::SS_FAILED) && + (nonZero->y.min() == 1.0) && (nonZero->y.max() == 1.0) && + (Gecode::PropagatorGroup::all.size(*nonZero) == 0); + delete nonZero; + if (!zeroRejected || !nonZeroAccepted) + olog << "Pow exponent-zero semantics failed" << std::endl; + return zeroRejected && nonZeroAccepted; + } + const bool result = + (direct.status() != Gecode::SS_FAILED) && + (direct.y.min() == 0.0) && (direct.y.max() == 0.0) && + (clone->status() != Gecode::SS_FAILED) && + (clone->y.min() == 0.0) && (clone->y.max() == 0.0); + delete clone; + if (!result) + olog << "Pow delayed zero failed for n=" << n << std::endl; + return result; + } + /// Test that a zero result forces a zero base + bool checkZeroResult(int n, bool direct) const { + ZeroResultSpace initial(n,direct); + if (initial.status() == Gecode::SS_FAILED) { + olog << "Pow zero result failed for n=" << n << std::endl; + return false; + } + ZeroResultSpace* clone = + static_cast(initial.clone()); + if (!direct) + Gecode::rel(*clone,clone->y,Gecode::FRT_EQ,0.0); + const bool contracted = + (clone->status() != Gecode::SS_FAILED) && + (clone->x.min() == 0.0) && (clone->x.max() == 0.0); + Gecode::rel(*clone,clone->x,Gecode::FRT_EQ,1.0); + const bool inconsistentRejected = + clone->status() == Gecode::SS_FAILED; + delete clone; + if (!contracted || !inconsistentRejected) + olog << "Pow zero result did not force zero base for n=" << n + << (direct ? " (direct)" : " (delayed)") << std::endl; + return contracted && inconsistentRejected; + } + /// Test exponent zero on an adjacent interval containing zero + bool checkAdjacentZero(bool positive) const { + AdjacentZeroSpace initial(positive); + const bool actorRetained = + (initial.status() != Gecode::SS_FAILED) && + (initial.y.min() == 1.0) && (initial.y.max() == 1.0) && + (Gecode::PropagatorGroup::all.size(initial) == 1); + + AdjacentZeroSpace* clone = + static_cast(initial.clone()); + const bool cloneRetained = + (clone->status() != Gecode::SS_FAILED) && + (clone->y.min() == 1.0) && (clone->y.max() == 1.0) && + (Gecode::PropagatorGroup::all.size(*clone) == 1); + delete clone; + + if (!actorRetained || !cloneRetained) + olog << "Pow adjacent exponent-zero semantics failed" + << (positive ? " (positive)" : " (negative)") << std::endl; + return actorRetained && cloneRetained; + } + public: + /// Create and register test + PowConsistency(void) : Base("Float::Arithmetic::PowConsistency") {} + /// Run zero and fixpoint regressions + virtual bool run(void) { + bool result = true; + for (int n=0; n<=3; n++) + result = checkZero(n) && result; + for (int n=1; n<=3; n++) { + result = checkZeroResult(n,true) && result; + result = checkZeroResult(n,false) && result; + } + result = checkAdjacentZero(true) && result; + result = checkAdjacentZero(false) && result; + + FixpointSpace once; + if (once.status() == Gecode::SS_FAILED) { + olog << "Pow fixpoint instance failed" << std::endl; + return false; + } + FixpointSpace* twice = static_cast(once.clone()); + Gecode::pow(*twice,twice->x,2,twice->y); + const bool fixpoint = + (twice->status() != Gecode::SS_FAILED) && + (once.x.min() == twice->x.min()) && + (once.x.max() == twice->x.max()) && + (once.y.min() == twice->y.min()) && + (once.y.max() == twice->y.max()); + delete twice; + if (!fixpoint) + olog << "Pow posting did not reach a fixpoint" << std::endl; + return fixpoint && result; + } + } pow_consistency; + + /// Regression tests for multiplication through a zero endpoint + class MultZeroEndpoint : public Base { + protected: + /// Space exposing multiplication propagation at the public API + class MultSpace : public Gecode::Space { + public: + /// Factors and product + Gecode::FloatVar x, y, z; + /// Post x * y = z for the supplied intervals + MultSpace(Gecode::FloatNum xl, Gecode::FloatNum xu, + Gecode::FloatNum yl, Gecode::FloatNum yu, + Gecode::FloatNum zl, Gecode::FloatNum zu) + : x(*this,xl,xu), y(*this,yl,yu), z(*this,zl,zu) { + Gecode::mult(*this,x,y,z); + } + /// Clone constructor + MultSpace(MultSpace& s) : Gecode::Space(s) { + x.update(*this,s.x); + y.update(*this,s.y); + z.update(*this,s.z); + } + /// Copy space during cloning + virtual Gecode::Space* copy(void) { + return new MultSpace(*this); + } + }; + + /// Check one endpoint-zero sign and magnitude contraction + bool check(Gecode::FloatNum xl, Gecode::FloatNum xu, + Gecode::FloatNum yl, Gecode::FloatNum yu, + Gecode::FloatNum zl, Gecode::FloatNum zu, + Gecode::FloatNum expectedMin, + Gecode::FloatNum expectedMax) const { + MultSpace s(xl,xu,yl,yu,zl,zu); + if ((s.status() == Gecode::SS_FAILED) || + (s.x.min() < expectedMin) || (s.x.max() > expectedMax)) { + olog << "Multiplication endpoint-zero contraction failed: x=" + << s.x << ", y=" << s.y << ", z=" << s.z << std::endl; + return false; + } + MultSpace* clone = static_cast(s.clone()); + Gecode::mult(*clone,clone->x,clone->y,clone->z); + const bool fixpoint = + (clone->status() != Gecode::SS_FAILED) && + (clone->x.min() == s.x.min()) && (clone->x.max() == s.x.max()) && + (clone->y.min() == s.y.min()) && (clone->y.max() == s.y.max()) && + (clone->z.min() == s.z.min()) && (clone->z.max() == s.z.max()); + delete clone; + if (!fixpoint) + olog << "Multiplication endpoint-zero case was not at a fixpoint" + << std::endl; + return fixpoint; + } + /// Exercise supported points across endpoint-zero sign combinations + bool fuzz(void) const { + unsigned int state = 0x6d756c74U; + for (unsigned int i=0; i<5000; i++) { + state = state * 1664525U + 1013904223U; + const Gecode::FloatNum am = + 0.125 + static_cast((state >> 8) % 8000) / + 512.0; + state = state * 1664525U + 1013904223U; + const Gecode::FloatNum bm = + 0.125 + static_cast((state >> 8) % 8000) / + 512.0; + const Gecode::FloatNum a = (i & 1U) ? -am : am; + const Gecode::FloatNum b = (i & 2U) ? -bm : bm; + const Gecode::FloatNum p = a * b; + const Gecode::FloatNum slack = 1.0 + am / 4.0; + const Gecode::FloatNum xl = a - slack; + const Gecode::FloatNum xu = a + slack; + const Gecode::FloatNum yl = (b < 0.0) ? -(bm + 1.0) : 0.0; + const Gecode::FloatNum yu = (b < 0.0) ? -0.0 : bm + 1.0; + MultSpace s(xl,xu,yl,yu,p,p); + if ((s.status() == Gecode::SS_FAILED) || + (s.x.min() > a) || (s.x.max() < a) || + (s.y.min() > b) || (s.y.max() < b) || + (s.z.min() > p) || (s.z.max() < p)) { + olog << "Multiplication removed supported fuzz point " << i + << ": (" << a << ", " << b << ", " << p << ")" + << std::endl; + return false; + } + } + return true; + } + public: + /// Create and register test + MultZeroEndpoint(void) + : Base("Float::Arithmetic::MultZeroEndpoint") {} + /// Run sign, symmetry, signed-zero, and zero-product cases + virtual bool run(void) { + bool result = true; + result = check(-1.0,1.0, 0.0,1.0, 0.5,1.0, + 0.5,1.0) && result; + result = check(-1.0,1.0,-1.0,-0.0, 0.5,1.0, + -1.0,-0.5) && result; + result = check(-1.0,1.0, 0.0,1.0,-1.0,-0.5, + -1.0,-0.5) && result; + result = check(-1.0,1.0,-1.0,-0.0,-1.0,-0.5, + 0.5,1.0) && result; + + // Swapping the factors must recover the same contraction. + MultSpace swapped(0.0,1.0,-1.0,1.0,0.5,1.0); + result = (swapped.status() != Gecode::SS_FAILED) && + (swapped.y.min() >= 0.5) && result; + + // A zero product gives no sign information about the other factor. + MultSpace zeroProduct(-1.0,1.0,0.0,1.0,0.0,0.0); + result = (zeroProduct.status() != Gecode::SS_FAILED) && + (zeroProduct.x.min() == -1.0) && + (zeroProduct.x.max() == 1.0) && result; + MultSpace zeroFactor(0.0,0.0,-1.0,1.0,0.0,0.0); + result = (zeroFactor.status() != Gecode::SS_FAILED) && + (zeroFactor.y.min() == -1.0) && + (zeroFactor.y.max() == 1.0) && result; + if (!result) + olog << "Multiplication zero or swapped regression failed" + << std::endl; + return fuzz() && result; + } + } mult_zero_endpoint; + /// %Test for multiplication constraint class MultXYZ : public Test { public: From c8d706f9c080864bb2c19490242eb309827038a0 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Fri, 10 Jul 2026 10:02:36 +0200 Subject: [PATCH 3/8] Harden CMake package consumption Resolve dependencies from the requested component closure, use same-minor package compatibility, and suppress header auto-linking for CMake consumers. Make custom VIS regeneration explicit and validate it with package, consumer, and test-selection smoke checks. --- .github/ci/cmake-consumer-smoke-vcpkg.ps1 | 21 ++- .github/ci/cmake-consumer-smoke.ps1 | 58 ++++++++- .github/ci/cmake-consumer-smoke.sh | 141 ++++++++++++++++++++- .github/ci/cmake-custom-vis-smoke.sh | 132 +++++++++++++++++++ .github/ci/cmake-package-version-checks.sh | 129 +++++++++++++++++++ CMakeLists.txt | 91 +++++++++++-- README.md | 4 +- cmake/GecodeConfig.cmake.in | 141 ++++++++++++++++----- cmake/VerifyTestSelection.cmake | 24 ++++ 9 files changed, 687 insertions(+), 54 deletions(-) create mode 100755 .github/ci/cmake-custom-vis-smoke.sh create mode 100755 .github/ci/cmake-package-version-checks.sh create mode 100644 cmake/VerifyTestSelection.cmake diff --git a/.github/ci/cmake-consumer-smoke-vcpkg.ps1 b/.github/ci/cmake-consumer-smoke-vcpkg.ps1 index f2c0edc203..0651b83175 100644 --- a/.github/ci/cmake-consumer-smoke-vcpkg.ps1 +++ b/.github/ci/cmake-consumer-smoke-vcpkg.ps1 @@ -1,5 +1,15 @@ $ErrorActionPreference = "Stop" +function Assert-NativeCommandSucceeded { + param( + [int] $ExitCode, + [string] $Description + ) + if ($ExitCode -ne 0) { + throw "$Description failed with exit code $ExitCode." + } +} + $prefix = $args[0] if (-not $prefix) { throw "usage: cmake-consumer-smoke-vcpkg.ps1 " @@ -111,10 +121,9 @@ cmake -S (Join-Path $work "a") -B (Join-Path $work "a\build") -G "Visual Studio "-DVCPKG_INSTALLED_DIR=$consumerVcpkgInstalled" ` "-DVCPKG_TARGET_TRIPLET=x64-windows" ` "-DCMAKE_PREFIX_PATH=$prefix" 2>&1 | Tee-Object -FilePath (Join-Path $work "a\configure.log") -if ($LASTEXITCODE -ne 0) { - throw "consumer_a configure failed." -} +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_a configure" cmake --build (Join-Path $work "a\build") --config $env:BUILD_TYPE +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_a build" cmake -S (Join-Path $work "b") -B (Join-Path $work "b\build") -G "Visual Studio 17 2022" -A x64 ` "-DCMAKE_TOOLCHAIN_FILE=$toolchain" ` @@ -123,7 +132,9 @@ cmake -S (Join-Path $work "b") -B (Join-Path $work "b\build") -G "Visual Studio "-DVCPKG_INSTALLED_DIR=$consumerVcpkgInstalled" ` "-DVCPKG_TARGET_TRIPLET=x64-windows" ` "-DCMAKE_PREFIX_PATH=$prefix" +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_b configure" cmake --build (Join-Path $work "b\build") --config $env:BUILD_TYPE +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_b build" cmake -S (Join-Path $work "c") -B (Join-Path $work "c\build") -G "Visual Studio 17 2022" -A x64 ` "-DCMAKE_TOOLCHAIN_FILE=$toolchain" ` @@ -132,7 +143,9 @@ cmake -S (Join-Path $work "c") -B (Join-Path $work "c\build") -G "Visual Studio "-DVCPKG_INSTALLED_DIR=$consumerVcpkgInstalled" ` "-DVCPKG_TARGET_TRIPLET=x64-windows" ` "-DCMAKE_PREFIX_PATH=$prefix" +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_c configure" cmake --build (Join-Path $work "c\build") --config $env:BUILD_TYPE +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_c build" cmake -S (Join-Path $work "d") -B (Join-Path $work "d\build") -G "Visual Studio 17 2022" -A x64 ` "-DCMAKE_TOOLCHAIN_FILE=$toolchain" ` @@ -141,7 +154,9 @@ cmake -S (Join-Path $work "d") -B (Join-Path $work "d\build") -G "Visual Studio "-DVCPKG_INSTALLED_DIR=$consumerVcpkgInstalled" ` "-DVCPKG_TARGET_TRIPLET=x64-windows" ` "-DCMAKE_PREFIX_PATH=$prefix" +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_d configure" cmake --build (Join-Path $work "d\build") --config $env:BUILD_TYPE +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_d build" if (-not (Select-String -Path (Join-Path $work "a\configure.log") -Pattern "Gecode_VERSION=" -Quiet)) { throw "Gecode_VERSION was not reported during consumer configure." diff --git a/.github/ci/cmake-consumer-smoke.ps1 b/.github/ci/cmake-consumer-smoke.ps1 index b87b14941f..24cd4c3ccb 100644 --- a/.github/ci/cmake-consumer-smoke.ps1 +++ b/.github/ci/cmake-consumer-smoke.ps1 @@ -1,5 +1,15 @@ $ErrorActionPreference = "Stop" +function Assert-NativeCommandSucceeded { + param( + [int] $ExitCode, + [string] $Description + ) + if ($ExitCode -ne 0) { + throw "$Description failed with exit code $ExitCode." + } +} + $prefix = $args[0] if (-not $prefix) { throw "usage: cmake-consumer-smoke.ps1 " @@ -7,7 +17,11 @@ if (-not $prefix) { $work = Join-Path $env:RUNNER_TEMP "gecode-consumers-win" if (Test-Path $work) { Remove-Item -Recurse -Force $work } -New-Item -ItemType Directory -Force -Path (Join-Path $work "a"), (Join-Path $work "b"), (Join-Path $work "c") | Out-Null +New-Item -ItemType Directory -Force -Path ` + (Join-Path $work "a"), ` + (Join-Path $work "b"), ` + (Join-Path $work "c"), ` + (Join-Path $work "manual") | Out-Null @' cmake_minimum_required(VERSION 3.21) @@ -52,14 +66,56 @@ int main(void) { } '@ | Set-Content -Encoding utf8 (Join-Path $work "c\main.cpp") +@' +cmake_minimum_required(VERSION 3.21) +project(manual_consumer LANGUAGES CXX) +set(GECODE_PREFIX "" CACHE PATH "Installed Gecode prefix") +find_path(GECODE_INCLUDE_DIR + NAMES gecode/support/config.hpp + PATHS "${GECODE_PREFIX}/include" + NO_DEFAULT_PATH + REQUIRED) +find_library(GECODE_SUPPORT_LIBRARY + NAMES gecodesupport + PATHS "${GECODE_PREFIX}/lib" + NO_DEFAULT_PATH + REQUIRED) +add_executable(manual_consumer main.cpp) +target_include_directories(manual_consumer PRIVATE "${GECODE_INCLUDE_DIR}") +target_link_libraries(manual_consumer PRIVATE "${GECODE_SUPPORT_LIBRARY}") +'@ | Set-Content -Encoding utf8 (Join-Path $work "manual\CMakeLists.txt") +@' +#include +#ifndef GECODE_NO_AUTOLINK +#error "CMake-installed headers must disable legacy MSVC auto-linking" +#endif +int main(void) { + (void) Gecode::Support::hwrnd(); + return 0; +} +'@ | Set-Content -Encoding utf8 (Join-Path $work "manual\main.cpp") + cmake -S (Join-Path $work "a") -B (Join-Path $work "a\build") -G "Visual Studio 17 2022" -A x64 "-DCMAKE_PREFIX_PATH=$prefix" 2>&1 | Tee-Object -FilePath (Join-Path $work "a\configure.log") +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_a configure" cmake --build (Join-Path $work "a\build") --config $env:BUILD_TYPE +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_a build" cmake -S (Join-Path $work "b") -B (Join-Path $work "b\build") -G "Visual Studio 17 2022" -A x64 "-DCMAKE_PREFIX_PATH=$prefix" +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_b configure" cmake --build (Join-Path $work "b\build") --config $env:BUILD_TYPE +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_b build" cmake -S (Join-Path $work "c") -B (Join-Path $work "c\build") -G "Visual Studio 17 2022" -A x64 "-DCMAKE_PREFIX_PATH=$prefix" +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_c configure" cmake --build (Join-Path $work "c\build") --config $env:BUILD_TYPE +Assert-NativeCommandSucceeded $LASTEXITCODE "consumer_c build" + +# Exercise the installed headers and library without loading the CMake package +# or inheriting compile definitions from an exported target. +cmake -S (Join-Path $work "manual") -B (Join-Path $work "manual\build") -G "Visual Studio 17 2022" -A x64 "-DGECODE_PREFIX=$prefix" +Assert-NativeCommandSucceeded $LASTEXITCODE "manual_consumer configure" +cmake --build (Join-Path $work "manual\build") --config $env:BUILD_TYPE +Assert-NativeCommandSucceeded $LASTEXITCODE "manual_consumer build" if (-not (Select-String -Path (Join-Path $work "a\configure.log") -Pattern "Gecode_VERSION=" -Quiet)) { throw "Gecode_VERSION was not reported during consumer configure." diff --git a/.github/ci/cmake-consumer-smoke.sh b/.github/ci/cmake-consumer-smoke.sh index 7a48fb00b5..c9488fb8ea 100644 --- a/.github/ci/cmake-consumer-smoke.sh +++ b/.github/ci/cmake-consumer-smoke.sh @@ -20,12 +20,16 @@ esac work="$RUNNER_TEMP/gecode-consumers" rm -rf "$work" mkdir -p "$work/a" "$work/b" "$work/c" "$work/d/cmake" "$work/e/cmake" "$work/e/stale" \ + "$work/f" "$work/g" "$work/h" "$work/i" "$work/j" "$work/k" \ "$work/mpfr-prefix/lib/cmake/MPFR" cat > "$work/a/CMakeLists.txt" <<'EOF' cmake_minimum_required(VERSION 3.21) project(consumer_a LANGUAGES CXX) find_package(Gecode CONFIG REQUIRED) +if(NOT Gecode_LIBRARIES STREQUAL "Gecode::gecode") + message(FATAL_ERROR "Expected aggregate Gecode_LIBRARIES, got: ${Gecode_LIBRARIES}") +endif() message(STATUS "Gecode_VERSION=${Gecode_VERSION}") message(STATUS "Gecode_INCLUDE_DIRS=${Gecode_INCLUDE_DIRS}") set(EXPECTED_GECODE_INCLUDE_DIR "" CACHE PATH "Expected Gecode include directory") @@ -58,6 +62,9 @@ cat > "$work/b/CMakeLists.txt" <<'EOF' cmake_minimum_required(VERSION 3.21) project(consumer_b LANGUAGES CXX) find_package(Gecode CONFIG REQUIRED COMPONENTS driver) +if(NOT Gecode_LIBRARIES STREQUAL "Gecode::gecodedriver") + message(FATAL_ERROR "Expected driver-only Gecode_LIBRARIES, got: ${Gecode_LIBRARIES}") +endif() add_executable(consumer_b main.cpp) target_link_libraries(consumer_b PRIVATE Gecode::gecodedriver) EOF @@ -73,6 +80,9 @@ cat > "$work/c/CMakeLists.txt" <<'EOF' cmake_minimum_required(VERSION 3.21) project(consumer_c LANGUAGES CXX) find_package(Gecode CONFIG REQUIRED COMPONENTS gecodedriver) +if(NOT Gecode_LIBRARIES STREQUAL "Gecode::gecodedriver") + message(FATAL_ERROR "Expected canonical library for legacy component, got: ${Gecode_LIBRARIES}") +endif() add_executable(consumer_c main.cpp) target_link_libraries(consumer_c PRIVATE Gecode::gecodedriver) EOF @@ -89,6 +99,9 @@ cmake_minimum_required(VERSION 3.21) project(consumer_d LANGUAGES CXX) list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") find_package(Gecode CONFIG REQUIRED COMPONENTS float) +if(NOT Gecode_LIBRARIES STREQUAL "Gecode::gecodefloat") + message(FATAL_ERROR "Expected float-only Gecode_LIBRARIES, got: ${Gecode_LIBRARIES}") +endif() if(NOT TARGET MPFR::MPFR) message(FATAL_ERROR "Expected Gecode package to provide MPFR::MPFR") endif() @@ -103,7 +116,7 @@ if(NOT gecodefloat_links MATCHES "MPFR::MPFR") message(FATAL_ERROR "Expected gecodefloat to link MPFR::MPFR, got: ${gecodefloat_links}") endif() add_executable(consumer_d main.cpp) -target_link_libraries(consumer_d PRIVATE Gecode::gecode) +target_link_libraries(consumer_d PRIVATE ${Gecode_LIBRARIES}) EOF cat > "$work/d/main.cpp" <<'EOF' #include @@ -155,6 +168,82 @@ set(MPFR_INCLUDE_DIRS "/bad/prior/mpfr/include") set(MPFR_LIBRARIES "/bad/prior/mpfr/lib/libmpfr.a") EOF +cat > "$work/f/CMakeLists.txt" <<'EOF' +cmake_minimum_required(VERSION 3.21) +project(consumer_f LANGUAGES CXX) +find_package(Gecode CONFIG REQUIRED COMPONENTS support) +if(NOT Gecode_LIBRARIES STREQUAL "Gecode::gecodesupport") + message(FATAL_ERROR "Expected support-only Gecode_LIBRARIES, got: ${Gecode_LIBRARIES}") +endif() +add_executable(consumer_f main.cpp) +target_link_libraries(consumer_f PRIVATE ${Gecode_LIBRARIES}) +EOF +cat > "$work/f/main.cpp" <<'EOF' +#include +int main(void) { return Gecode::Support::Thread::npu() > 0 ? 0 : 1; } +EOF + +cat > "$work/g/CMakeLists.txt" <<'EOF' +cmake_minimum_required(VERSION 3.21) +project(consumer_g LANGUAGES CXX) +find_package(Gecode CONFIG REQUIRED COMPONENTS gecodesupport) +if(NOT Gecode_LIBRARIES STREQUAL "Gecode::gecodesupport") + message(FATAL_ERROR "Expected canonical library for legacy component, got: ${Gecode_LIBRARIES}") +endif() +add_executable(consumer_g main.cpp) +target_link_libraries(consumer_g PRIVATE ${Gecode_LIBRARIES}) +EOF +cat > "$work/g/main.cpp" <<'EOF' +#include +int main(void) { return 0; } +EOF + +cat > "$work/h/CMakeLists.txt" <<'EOF' +cmake_minimum_required(VERSION 3.21) +project(consumer_h LANGUAGES CXX) +find_package(Gecode CONFIG REQUIRED COMPONENTS unknown_component) +EOF + +cat > "$work/i/CMakeLists.txt" <<'EOF' +cmake_minimum_required(VERSION 3.21) +project(consumer_i LANGUAGES CXX) +find_package(Gecode CONFIG REQUIRED COMPONENTS float) +EOF + +cat > "$work/j/CMakeLists.txt" <<'EOF' +cmake_minimum_required(VERSION 3.21) +project(consumer_j LANGUAGES CXX) +find_package(Gecode CONFIG REQUIRED COMPONENTS gist) +if(NOT Gecode_LIBRARIES STREQUAL "Gecode::gecodegist") + message(FATAL_ERROR "Expected gist-only Gecode_LIBRARIES, got: ${Gecode_LIBRARIES}") +endif() +add_executable(consumer_j main.cpp) +target_link_libraries(consumer_j PRIVATE ${Gecode_LIBRARIES}) +EOF +cat > "$work/j/main.cpp" <<'EOF' +#include +int main(void) { return 0; } +EOF + +cat > "$work/k/CMakeLists.txt" <<'EOF' +cmake_minimum_required(VERSION 3.21) +project(consumer_k LANGUAGES CXX) +find_package(Gecode CONFIG REQUIRED COMPONENTS support) +find_package(Gecode CONFIG REQUIRED COMPONENTS float) +if(NOT TARGET MPFR::MPFR) + message(FATAL_ERROR "Expected the second component request to resolve MPFR") +endif() +if(NOT Gecode_LIBRARIES STREQUAL "Gecode::gecodefloat") + message(FATAL_ERROR "Expected the second request to replace Gecode_LIBRARIES, got: ${Gecode_LIBRARIES}") +endif() +add_executable(consumer_k main.cpp) +target_link_libraries(consumer_k PRIVATE ${Gecode_LIBRARIES}) +EOF +cat > "$work/k/main.cpp" <<'EOF' +#include +int main(void) { Gecode::Float::Rounding rounding; return rounding.sqrt_down(4.0) == 2.0 ? 0 : 1; } +EOF + cat > "$work/mpfr-prefix/lib/cmake/MPFR/MPFRConfig.cmake" <<'EOF' get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) if(NOT TARGET MPFR::MPFR) @@ -182,16 +271,66 @@ cmake --build "$work/b/build" -j4 cmake -S "$work/c" -B "$work/c/build" -DCMAKE_PREFIX_PATH="$prefix" cmake --build "$work/c/build" -j4 +# A support-only consumer must not discover dependencies belonging exclusively +# to Float, FlatZinc, or Gist, even when those targets exist in the package. +cmake -S "$work/f" -B "$work/f/build" \ + -DCMAKE_PREFIX_PATH="$prefix" \ + -DCMAKE_DISABLE_FIND_PACKAGE_MPFR=TRUE \ + -DCMAKE_DISABLE_FIND_PACKAGE_Qt5=TRUE \ + -DCMAKE_DISABLE_FIND_PACKAGE_Qt6=TRUE +cmake --build "$work/f/build" -j4 + +cmake -S "$work/g" -B "$work/g/build" \ + -DCMAKE_PREFIX_PATH="$prefix" \ + -DCMAKE_DISABLE_FIND_PACKAGE_MPFR=TRUE \ + -DCMAKE_DISABLE_FIND_PACKAGE_Qt5=TRUE \ + -DCMAKE_DISABLE_FIND_PACKAGE_Qt6=TRUE +cmake --build "$work/g/build" -j4 + +if cmake -S "$work/h" -B "$work/h/build" -DCMAKE_PREFIX_PATH="$prefix"; then + echo "Unknown required Gecode component unexpectedly succeeded" >&2 + exit 1 +fi + if [ "$mode" = "mpfr" ]; then cmake -S "$work/d" -B "$work/d/build" -DCMAKE_PREFIX_PATH="$prefix" cmake --build "$work/d/build" -j4 cmake -S "$work/e" -B "$work/e/build" -DCMAKE_PREFIX_PATH="$prefix" cmake --build "$work/e/build" -j4 + + cmake -S "$work/k" -B "$work/k/build" -DCMAKE_PREFIX_PATH="$prefix" + cmake --build "$work/k/build" -j4 + + if cmake -S "$work/i" -B "$work/i/build" \ + -DCMAKE_PREFIX_PATH="$prefix" \ + -DCMAKE_DISABLE_FIND_PACKAGE_MPFR=TRUE; then + echo "Float component unexpectedly succeeded without MPFR" >&2 + exit 1 + fi +fi + +targets_file="$(find "$prefix" -name GecodeTargets.cmake -print -quit)" +if grep -q 'add_library(Gecode::gecodegist ' "$targets_file"; then + if cmake -S "$work/j" -B "$work/j/no-qt-build" \ + -DCMAKE_PREFIX_PATH="$prefix" \ + -DCMAKE_DISABLE_FIND_PACKAGE_Qt5=TRUE \ + -DCMAKE_DISABLE_FIND_PACKAGE_Qt6=TRUE; then + echo "Gist component unexpectedly succeeded without Qt" >&2 + exit 1 + fi + cmake -S "$work/j" -B "$work/j/build" -DCMAKE_PREFIX_PATH="$prefix" + cmake --build "$work/j/build" -j4 +else + if cmake -S "$work/j" -B "$work/j/missing-build" -DCMAKE_PREFIX_PATH="$prefix"; then + echo "Missing required Gist component unexpectedly succeeded" >&2 + exit 1 + fi fi grep -q "Gecode_VERSION=" "$work/a/configure.log" grep -q "Gecode_INCLUDE_DIRS=$expected_include" "$work/a/configure.log" +grep -q '^#define GECODE_NO_AUTOLINK 1$' "$expected_include/gecode/support/config.hpp" if [ "$mode" = "mpfr" ]; then grep -q '^#define GECODE_HAS_MPFR /\*\*/' "$expected_include/gecode/support/config.hpp" fi diff --git a/.github/ci/cmake-custom-vis-smoke.sh b/.github/ci/cmake-custom-vis-smoke.sh new file mode 100755 index 0000000000..cfca5d592e --- /dev/null +++ b/.github/ci/cmake-custom-vis-smoke.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +set -euxo pipefail + +repo_root="$(git rev-parse --show-toplevel)" +temp_root="${RUNNER_TEMP:-${TMPDIR:-/tmp}}" +work_parent="" +fixture_dir="" + +cleanup() { + if [[ -n "$fixture_dir" && + "$fixture_dir" == "$repo_root"/.cmake-custom-vis-smoke.* ]]; then + rm -rf "$fixture_dir" + fi + if [[ -n "$work_parent" && + "$work_parent" == "$temp_root"/gecode-cmake-custom-vis-smoke.* ]]; then + rm -rf "$work_parent" + fi +} +trap cleanup EXIT + +work_parent="$(mktemp -d "$temp_root/gecode-cmake-custom-vis-smoke.XXXXXX")" +fixture_dir="$(mktemp -d "$repo_root/.cmake-custom-vis-smoke.XXXXXX")" +fixture_relative="${fixture_dir#"$repo_root"/}" +build_dir="$work_parent/build" +mkdir -p "$fixture_dir/custom vis" + +awk ' + /^Name:[[:space:]]*Int$/ { print "Name: CustomOne"; next } + /^Namespace:[[:space:]]*Gecode::Int$/ { print "Namespace: Gecode::CustomOne"; next } + /^Ifdef:[[:space:]]*GECODE_HAS_INT_VARS$/ { next } + { print } +' "$repo_root/gecode/int/var-imp/int.vis" > "$fixture_dir/custom vis/custom one.vis" + +awk ' + /^Name:[[:space:]]*Int$/ { print "Name: CustomTwo"; next } + /^Namespace:[[:space:]]*Gecode::Int$/ { print "Namespace: Gecode::CustomTwo"; next } + /^Ifdef:[[:space:]]*GECODE_HAS_INT_VARS$/ { next } + { print } +' "$repo_root/gecode/int/var-imp/int.vis" > "$fixture_dir/custom-two.vis" + +source_type_hash="$(cmake -E sha256sum "$repo_root/gecode/kernel/var-type.hpp")" +source_imp_hash="$(cmake -E sha256sum "$repo_root/gecode/kernel/var-imp.hpp")" + +cmake -S "$repo_root" -B "$build_dir" -G Ninja \ + -DGECODE_WITH_VIS="$fixture_relative/custom vis/custom one.vis,$fixture_dir/custom-two.vis" \ + -DGECODE_REGENERATE_VARIMP=OFF \ + -DGECODE_ENABLE_EXAMPLES=OFF \ + -DGECODE_ENABLE_QT=OFF \ + -DGECODE_ENABLE_GIST=OFF \ + -DBUILD_TESTING=OFF + +grep -q '^GECODE_REGENERATE_VARIMP:BOOL=OFF$' "$build_dir/CMakeCache.txt" +cmake --build "$build_dir" --target gecode-varimp-gen -v + +type_header="$build_dir/gecode/kernel/var-type.hpp" +imp_header="$build_dir/gecode/kernel/var-imp.hpp" +grep -q 'CustomOneVarImpConf' "$type_header" +grep -q 'CustomTwoVarImpConf' "$type_header" +grep -q 'CustomOneVarImp' "$imp_header" +grep -q 'CustomTwoVarImp' "$imp_header" + +custom_one_line="$(grep -n -m1 'class CustomOneVarImpConf' "$type_header" | cut -d: -f1)" +custom_two_line="$(grep -n -m1 'class CustomTwoVarImpConf' "$type_header" | cut -d: -f1)" +int_line="$(grep -n -m1 'class IntVarImpConf' "$type_header" | cut -d: -f1)" +bool_line="$(grep -n -m1 'class BoolVarImpConf' "$type_header" | cut -d: -f1)" +set_line="$(grep -n -m1 'class SetVarImpConf' "$type_header" | cut -d: -f1)" +float_line="$(grep -n -m1 'class FloatVarImpConf' "$type_header" | cut -d: -f1)" +test "$int_line" -lt "$bool_line" +test "$bool_line" -lt "$set_line" +test "$set_line" -lt "$float_line" +test "$float_line" -lt "$custom_one_line" +test "$custom_one_line" -lt "$custom_two_line" + +mtime() { + stat -f '%m' "$1" 2>/dev/null || stat -c '%Y' "$1" +} +type_mtime="$(mtime "$type_header")" +imp_mtime="$(mtime "$imp_header")" +cmake -E sleep 1 +printf '\n' >> "$fixture_dir/custom vis/custom one.vis" +cmake --build "$build_dir" --target gecode-varimp-gen -v 2>&1 | tee "$work_parent/rebuild.log" +test "$(mtime "$type_header")" -gt "$type_mtime" +test "$(mtime "$imp_header")" -gt "$imp_mtime" +grep -q 'GenerateVarImp.cmake' "$work_parent/rebuild.log" + +# CMake list syntax is supported in addition to the command-line comma form. +cmake -S "$repo_root" -B "$work_parent/list-build" -G Ninja \ + -DGECODE_WITH_VIS="$fixture_relative/custom vis/custom one.vis;$fixture_dir/custom-two.vis" \ + -DGECODE_ENABLE_EXAMPLES=OFF \ + -DGECODE_ENABLE_QT=OFF \ + -DGECODE_ENABLE_GIST=OFF \ + -DBUILD_TESTING=OFF +cmake --build "$work_parent/list-build" --target gecode-varimp-gen + +if cmake -S "$repo_root" -B "$work_parent/missing-build" -G Ninja \ + -DGECODE_WITH_VIS="$fixture_dir/does-not-exist.vis" \ + -DGECODE_ENABLE_EXAMPLES=OFF -DBUILD_TESTING=OFF \ + >"$work_parent/missing.log" 2>&1; then + echo "Missing GECODE_WITH_VIS file unexpectedly configured" >&2 + exit 1 +fi +grep -q "does not exist" "$work_parent/missing.log" +grep -q "does-not-exist.vis" "$work_parent/missing.log" + +if cmake -S "$repo_root" -B "$work_parent/directory-build" -G Ninja \ + -DGECODE_WITH_VIS="$fixture_dir/custom vis" \ + -DGECODE_ENABLE_EXAMPLES=OFF -DBUILD_TESTING=OFF \ + >"$work_parent/directory.log" 2>&1; then + echo "Directory GECODE_WITH_VIS entry unexpectedly configured" >&2 + exit 1 +fi +grep -q "directory; expected a .vis file" "$work_parent/directory.log" + +test "$source_type_hash" = "$(cmake -E sha256sum "$repo_root/gecode/kernel/var-type.hpp")" +test "$source_imp_hash" = "$(cmake -E sha256sum "$repo_root/gecode/kernel/var-imp.hpp")" + +# Clearing the custom list restores the user's cached OFF choice and makes the +# generation target a no-op in the same build tree. +cmake -S "$repo_root" -B "$build_dir" -G Ninja \ + -DGECODE_WITH_VIS= \ + -DGECODE_REGENERATE_VARIMP=OFF \ + -DGECODE_ENABLE_EXAMPLES=OFF \ + -DGECODE_ENABLE_QT=OFF \ + -DGECODE_ENABLE_GIST=OFF \ + -DBUILD_TESTING=OFF +grep -q '^GECODE_REGENERATE_VARIMP:BOOL=OFF$' "$build_dir/CMakeCache.txt" +rm -f "$type_header" "$imp_header" +cmake --build "$build_dir" --target gecode-varimp-gen -v \ + >"$work_parent/cleared.log" 2>&1 +test ! -e "$type_header" +test ! -e "$imp_header" +grep -q 'no work to do' "$work_parent/cleared.log" diff --git a/.github/ci/cmake-package-version-checks.sh b/.github/ci/cmake-package-version-checks.sh new file mode 100755 index 0000000000..396502d0ba --- /dev/null +++ b/.github/ci/cmake-package-version-checks.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +set -euo pipefail + +prefix="${1:?usage: cmake-package-version-checks.sh }" +temp_root="${RUNNER_TEMP:-${TMPDIR:-/tmp}}" +if [ ! -d "$temp_root" ]; then + echo "Temporary root is not a directory: $temp_root" >&2 + exit 1 +fi + +work= +workspace= +cleanup() { + if [ -n "$work" ]; then + rm -rf -- "$work" + fi +} +trap cleanup EXIT + +# Record the cleanup boundary before creating anything inside it. In +# particular, a signal delivered after mktemp creates its directory but before +# command substitution assigns its output can still remove this known parent. +# Ignore termination signals only while ownership of the parent is being +# established. This prevents cleanup from deleting a colliding path and also +# prevents a signal between mkdir and assignment from leaking a directory we +# just created. +trap '' HUP INT TERM +umask 077 +for attempt in {1..100}; do + candidate="$temp_root/gecode-package-version-checks.${BASHPID:-$$}.$RANDOM.$RANDOM.$attempt" + if mkdir -m 700 "$candidate"; then + work=$candidate + break + fi +done +unset candidate +trap 'exit 1' HUP INT TERM +if [ -z "$work" ]; then + echo "Could not create a private package-version workspace below $temp_root" >&2 + exit 1 +fi +workspace=$(mktemp -d "$work/run.XXXXXX") +config_version=$(find "$prefix" -type f -name GecodeConfigVersion.cmake -print -quit) + +if [ -z "$config_version" ]; then + echo "GecodeConfigVersion.cmake not found below $prefix" >&2 + exit 1 +fi + +config_dir=$(dirname "$config_version") +version=$(sed -n 's/^set(PACKAGE_VERSION "\([0-9][0-9.]*\)")$/\1/p' "$config_version" | head -n 1) +IFS=. read -r major minor patch <<<"$version" + +if [ -z "${major:-}" ] || [ -z "${minor:-}" ] || [ -z "${patch:-}" ]; then + echo "Could not read a three-part package version from $config_version" >&2 + exit 1 +fi + +previous_minor=$((minor - 1)) +next_minor=$((minor + 1)) +next_patch=$((patch + 1)) + +mkdir -p "$workspace/source" + +cat >"$workspace/source/CMakeLists.txt" <<'EOF' +cmake_minimum_required(VERSION 3.21) +project(GecodePackageVersionCheck LANGUAGES CXX) + +if(REQUEST_MODE STREQUAL "NONE") + find_package(Gecode CONFIG REQUIRED) +elseif(REQUEST_MODE STREQUAL "EXACT") + find_package(Gecode ${REQUEST_VERSION} EXACT CONFIG REQUIRED) +else() + find_package(Gecode ${REQUEST_VERSION} CONFIG REQUIRED) +endif() +EOF + +configure_case() { + name=$1 + package_dir=$2 + mode=$3 + requested=${4:-} + if [ -n "$requested" ]; then + cmake -S "$workspace/source" -B "$workspace/$name" \ + -DGecode_DIR="$package_dir" \ + -DREQUEST_MODE="$mode" \ + -DREQUEST_VERSION="$requested" + else + cmake -S "$workspace/source" -B "$workspace/$name" \ + -DGecode_DIR="$package_dir" \ + -DREQUEST_MODE="$mode" + fi +} + +check_accepts() { + if ! configure_case "$@"; then + echo "Gecode package unexpectedly rejected the $1 case" >&2 + exit 1 + fi +} + +check_rejects() { + name=$1 + requested=$2 + if configure_case "$name" "$config_dir" NORMAL "$requested"; then + echo "Gecode $version unexpectedly satisfied request $requested" >&2 + exit 1 + fi +} + +check_accepts no-version "$config_dir" NONE +check_accepts exact-version "$config_dir" EXACT "$version" +check_rejects previous-minor "$major.$previous_minor.0" +check_rejects next-minor "$major.$next_minor.0" +check_rejects newer-patch "$major.$minor.$next_patch" + +# A newer patch release in the same minor line must satisfy this package's +# version, while still rejecting an exact request for the older patch. +mkdir -p "$workspace/newer-patch-package" +cat >"$workspace/newer-patch-package/GecodeConfig.cmake" <"$workspace/newer-patch-package/GecodeConfigVersion.cmake" +check_accepts older-patch-request "$workspace/newer-patch-package" NORMAL "$version" +if configure_case exact-older-patch-request "$workspace/newer-patch-package" EXACT "$version"; then + echo "A newer patch unexpectedly satisfied an EXACT request for $version" >&2 + exit 1 +fi diff --git a/CMakeLists.txt b/CMakeLists.txt index b8cad38e5b..865522b7f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,6 +136,12 @@ set(GECODE_WITH_VIS "" CACHE STRING "Additional .vis files (comma-separated)") set(GECODE_LIB_PREFIX "" CACHE STRING "User-defined prefix for generated library basenames") set(GECODE_LIB_SUFFIX "" CACHE STRING "User-defined suffix for generated library basenames") +if(NOT GECODE_WITH_VIS STREQUAL "" AND NOT GECODE_REGENERATE_VARIMP) + set(GECODE_REGENERATE_VARIMP ON) + message(STATUS + "GECODE_WITH_VIS activates GECODE_REGENERATE_VARIMP for generated headers") +endif() + # Deprecated compatibility aliases for the Gecode 6.x line. if(GECODE_USED_ENABLE_THREADS_ALIAS) set(GECODE_ENABLE_THREAD ${GECODE_ALIAS_ENABLE_THREAD_VALUE} CACHE BOOL "Enable thread support" FORCE) @@ -526,6 +532,9 @@ string(REGEX MATCHALL "[^\n]*\n" CONFIG /* Define to 1 if you have a working `mmap' system call. */ #undef HAVE_MMAP +/* Whether legacy MSVC automatic library selection is disabled. */ +#undef GECODE_NO_AUTOLINK + /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT @@ -584,6 +593,10 @@ endif() set(GECODE_DLL_USERPREFIX "${GECODE_LIB_PREFIX}") set(GECODE_DLL_USERSUFFIX "${GECODE_LIB_SUFFIX}") +# CMake consumers link explicitly, either through the exported targets or by +# naming the installed libraries. Keep the installed headers from also +# injecting the legacy, Autoconf-specific MSVC library names. +set(GECODE_NO_AUTOLINK 1) if(GECODE_ENABLE_INT_VARS) set(GECODE_HAS_INT_VARS "/**/") endif() @@ -714,7 +727,12 @@ if(GECODE_ENABLE_FLOAT_VARS) endif() if(GECODE_WITH_VIS) string(REPLACE "," ";" _extra_vis "${GECODE_WITH_VIS}") - foreach(vis ${_extra_vis}) + foreach(vis IN LISTS _extra_vis) + string(STRIP "${vis}" vis) + if(vis STREQUAL "") + message(FATAL_ERROR + "GECODE_WITH_VIS contains an empty variable implementation specification") + endif() list(APPEND GECODE_VIS_FILES "${vis}") endforeach() endif() @@ -727,19 +745,28 @@ endif() set(GECODE_VIS_DEPENDS) set(GECODE_VIS_FILES_FOR_GEN) -foreach(vis ${GECODE_VIS_FILES}) +foreach(vis IN LISTS GECODE_VIS_FILES) if(IS_ABSOLUTE "${vis}") - list(APPEND GECODE_VIS_DEPENDS "${vis}") - list(APPEND GECODE_VIS_FILES_FOR_GEN "${vis}") + set(_vis_path "${vis}") else() - list(APPEND GECODE_VIS_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${vis}") - if(vis MATCHES "^\\./") - list(APPEND GECODE_VIS_FILES_FOR_GEN "${vis}") - else() - list(APPEND GECODE_VIS_FILES_FOR_GEN "./${vis}") - endif() + set(_vis_path "${CMAKE_CURRENT_SOURCE_DIR}/${vis}") endif() + cmake_path(NORMAL_PATH _vis_path) + if(NOT EXISTS "${_vis_path}") + message(FATAL_ERROR + "Variable implementation specification '${vis}' does not exist " + "(resolved to '${_vis_path}')") + endif() + if(IS_DIRECTORY "${_vis_path}") + message(FATAL_ERROR + "Variable implementation specification '${vis}' is a directory; " + "expected a .vis file") + endif() + list(APPEND GECODE_VIS_DEPENDS "${_vis_path}") + list(APPEND GECODE_VIS_FILES_FOR_GEN "${_vis_path}") endforeach() +list(REMOVE_DUPLICATES GECODE_VIS_DEPENDS) +list(REMOVE_DUPLICATES GECODE_VIS_FILES_FOR_GEN) string(JOIN "||" GECODE_VIS_FILES_SERIALIZED ${GECODE_VIS_FILES_FOR_GEN}) set(GECODE_VAR_TYPE_HPP_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/gecode/kernel/var-type.hpp) @@ -1257,8 +1284,35 @@ if(BUILD_TESTING) Set::Sequence::SeqU1 Set::Wait) endif() + if(GECODE_ENABLE_FLOAT_VARS) + set(GECODE_FLOAT_CHECK_TESTS + Float::Arithmetic::PositiveNRootBounds + Float::Arithmetic::PowConsistency + Float::Arithmetic::MultZeroEndpoint + Float::Arithmetic::Pow::N::2::XY::Sol::C + Float::Arithmetic::NRoot::N::2::XY::Sol::C + Float::Arithmetic::Mult::XYZ::Sol::C) + list(APPEND GECODE_CHECK_TESTS ${GECODE_FLOAT_CHECK_TESTS}) + + # Keep the fast CI selection honest when Float regressions are added or + # the list above is edited. These names must remain registered in the + # ordinary check target rather than living only in ad-hoc test commands. + set(GECODE_REQUIRED_FLOAT_CHECK_TESTS + Float::Arithmetic::PositiveNRootBounds + Float::Arithmetic::PowConsistency + Float::Arithmetic::MultZeroEndpoint + Float::Arithmetic::Pow::N::2::XY::Sol::C + Float::Arithmetic::NRoot::N::2::XY::Sol::C + Float::Arithmetic::Mult::XYZ::Sol::C) + foreach(gecode_required_float_test ${GECODE_REQUIRED_FLOAT_CHECK_TESTS}) + if(NOT gecode_required_float_test IN_LIST GECODE_CHECK_TESTS) + message(FATAL_ERROR + "Required Float check test is missing: ${gecode_required_float_test}") + endif() + endforeach() + endif() - set(GECODE_CHECK_ARGS -iter 2 -threads 0) + set(GECODE_CHECK_ARGS -iter 2 -threads 0 -fixprob 1) foreach(gecode_check_test ${GECODE_CHECK_TESTS}) list(APPEND GECODE_CHECK_ARGS -test ${gecode_check_test}) endforeach() @@ -1272,9 +1326,20 @@ if(BUILD_TESTING) add_test(NAME test COMMAND gecode-test ${GECODE_CHECK_ARGS}) set_tests_properties(test PROPERTIES FIXTURES_REQUIRED gecode-test-built) + set(GECODE_CHECK_DEPENDS gecode-test) + if(GECODE_ENABLE_FLOAT_VARS) + add_custom_target(verify-gecode-check-tests + COMMAND ${CMAKE_COMMAND} + "-DTEST_EXECUTABLE=$" + "-DREQUIRED_TESTS=${GECODE_REQUIRED_FLOAT_CHECK_TESTS}" + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/VerifyTestSelection.cmake + DEPENDS gecode-test + VERBATIM) + list(APPEND GECODE_CHECK_DEPENDS verify-gecode-check-tests) + endif() add_custom_target(check COMMAND $ ${GECODE_CHECK_ARGS} - DEPENDS gecode-test + DEPENDS ${GECODE_CHECK_DEPENDS} USES_TERMINAL) else() message(WARNING "Skipping gecode-test/check targets because required modules are disabled") @@ -1419,7 +1484,7 @@ if(GECODE_INSTALL) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/GecodeConfigVersion.cmake VERSION ${GECODE_PROJECT_VERSION} - COMPATIBILITY SameMajorVersion) + COMPATIBILITY SameMinorVersion) install(EXPORT GecodeTargets FILE GecodeTargets.cmake diff --git a/README.md b/README.md index 0b3f5112eb..c26e8aeda7 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,9 @@ Version metadata shared by autoconf and CMake lives in `gecode-version.m4`. By default, CMake uses checked-in `gecode/kernel/var-type.hpp` and `gecode/kernel/var-imp.hpp`; regeneration is opt-in via -`-DGECODE_REGENERATE_VARIMP=ON`. +`-DGECODE_REGENERATE_VARIMP=ON`. A nonempty `GECODE_WITH_VIS` list enables +regeneration automatically and validates every specification during +configuration; relative paths are resolved from the source directory. Build-time script execution uses `uv run --script ...` and requires Python 3.11 or newer. Autoconf builds require `uv` on `PATH`; CMake requires it only for generated-script paths such as `-DGECODE_REGENERATE_VARIMP=ON` and the diff --git a/cmake/GecodeConfig.cmake.in b/cmake/GecodeConfig.cmake.in index e7c8327bed..4099c3b2bd 100644 --- a/cmake/GecodeConfig.cmake.in +++ b/cmake/GecodeConfig.cmake.in @@ -2,11 +2,97 @@ set(_gecode_package_prefix_dir "${PACKAGE_PREFIX_DIR}") +set(_gecode_supported_components + support + kernel + search + int + set + float + minimodel + driver + flatzinc + gist) +set(_gecode_built_components "@GECODE_LIBRARY_COMPONENTS@") + +foreach(_gecode_component IN LISTS _gecode_supported_components) + if(_gecode_component IN_LIST _gecode_built_components) + set(_gecode_component_found TRUE) + else() + set(_gecode_component_found FALSE) + endif() + set(Gecode_${_gecode_component}_FOUND ${_gecode_component_found}) + set(Gecode_gecode${_gecode_component}_FOUND ${_gecode_component_found}) +endforeach() + +set(_gecode_dependency_roots) +set(Gecode_LIBRARIES) +if(Gecode_FIND_COMPONENTS) + foreach(_gecode_requested_component IN LISTS Gecode_FIND_COMPONENTS) + set(_gecode_canonical_component "${_gecode_requested_component}") + if(_gecode_canonical_component MATCHES "^gecode(.+)$") + set(_gecode_canonical_component "${CMAKE_MATCH_1}") + endif() + if(_gecode_canonical_component IN_LIST _gecode_supported_components) + if(_gecode_canonical_component IN_LIST _gecode_built_components) + list(APPEND _gecode_dependency_roots ${_gecode_canonical_component}) + list(APPEND Gecode_LIBRARIES "Gecode::gecode${_gecode_canonical_component}") + endif() + elseif(NOT DEFINED Gecode_${_gecode_requested_component}_FOUND) + set(Gecode_${_gecode_requested_component}_FOUND FALSE) + endif() + endforeach() + list(REMOVE_DUPLICATES _gecode_dependency_roots) + list(REMOVE_DUPLICATES Gecode_LIBRARIES) +else() + set(_gecode_dependency_roots ${_gecode_built_components}) + set(Gecode_LIBRARIES Gecode::gecode) +endif() + +# Compute the transitive component closure before importing targets. Importing +# an unused target whose interface names an unavailable external target can +# otherwise make CMake's generate step fail for a support-only consumer. +set(_gecode_dependency_queue ${_gecode_dependency_roots}) +set(_gecode_component_closure) +while(_gecode_dependency_queue) + list(POP_FRONT _gecode_dependency_queue _gecode_dependency_component) + if(_gecode_dependency_component IN_LIST _gecode_component_closure) + continue() + endif() + list(APPEND _gecode_component_closure ${_gecode_dependency_component}) + set(_gecode_component_dependencies) + if(_gecode_dependency_component STREQUAL kernel) + set(_gecode_component_dependencies support) + elseif(_gecode_dependency_component STREQUAL search OR + _gecode_dependency_component STREQUAL int) + set(_gecode_component_dependencies kernel) + elseif(_gecode_dependency_component STREQUAL set) + set(_gecode_component_dependencies int) + elseif(_gecode_dependency_component STREQUAL float) + set(_gecode_component_dependencies int kernel) + elseif(_gecode_dependency_component STREQUAL minimodel) + set(_gecode_component_dependencies int set search float) + elseif(_gecode_dependency_component STREQUAL gist) + set(_gecode_component_dependencies search int set float) + elseif(_gecode_dependency_component STREQUAL driver) + set(_gecode_component_dependencies int search minimodel gist) + elseif(_gecode_dependency_component STREQUAL flatzinc) + set(_gecode_component_dependencies minimodel driver gist) + endif() + foreach(_gecode_component_dependency IN LISTS _gecode_component_dependencies) + if(_gecode_component_dependency IN_LIST _gecode_built_components) + list(APPEND _gecode_dependency_queue ${_gecode_component_dependency}) + endif() + endforeach() +endwhile() + include(CMakeFindDependencyMacro) -if("@GECODE_PACKAGE_NEEDS_THREADS@" STREQUAL "ON") +if(support IN_LIST _gecode_component_closure AND + "@GECODE_PACKAGE_NEEDS_THREADS@" STREQUAL "ON") find_dependency(Threads) endif() -if("@GECODE_PACKAGE_NEEDS_MPFR@" STREQUAL "ON") +if(float IN_LIST _gecode_component_closure AND + "@GECODE_PACKAGE_NEEDS_MPFR@" STREQUAL "ON") set(_gecode_mpfr_args) if(Gecode_FIND_QUIETLY) list(APPEND _gecode_mpfr_args QUIET) @@ -69,9 +155,15 @@ if("@GECODE_PACKAGE_NEEDS_MPFR@" STREQUAL "ON") endif() set(_gecode_qt_components "@GECODE_PACKAGE_QT_COMPONENTS@") set(_gecode_qt_version "@GECODE_PACKAGE_QT_VERSION@") -if("@GECODE_PACKAGE_QT_MAJOR@" STREQUAL "6") +set(_gecode_needs_qt FALSE) +if(gist IN_LIST _gecode_component_closure OR + (flatzinc IN_LIST _gecode_component_closure AND + NOT "@GECODE_PACKAGE_QT_MAJOR@" STREQUAL "")) + set(_gecode_needs_qt TRUE) +endif() +if(_gecode_needs_qt AND "@GECODE_PACKAGE_QT_MAJOR@" STREQUAL "6") find_dependency(Qt6 ${_gecode_qt_version} COMPONENTS ${_gecode_qt_components}) -elseif("@GECODE_PACKAGE_QT_MAJOR@" STREQUAL "5") +elseif(_gecode_needs_qt AND "@GECODE_PACKAGE_QT_MAJOR@" STREQUAL "5") find_dependency(Qt5 ${_gecode_qt_version} COMPONENTS ${_gecode_qt_components}) endif() unset(_gecode_qt_version) @@ -82,41 +174,20 @@ include("${CMAKE_CURRENT_LIST_DIR}/GecodeTargets.cmake") set(Gecode_VERSION "@GECODE_PROJECT_VERSION@") set_and_check(Gecode_INCLUDE_DIRS "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@") -set(Gecode_LIBRARIES Gecode::gecode) -unset(_gecode_package_prefix_dir) - -set(_gecode_supported_components - support - kernel - search - int - set - float - minimodel - driver - flatzinc - gist) - -foreach(_gecode_component IN LISTS _gecode_supported_components) - set(_gecode_component_target "Gecode::gecode${_gecode_component}") - if(TARGET ${_gecode_component_target}) - set(_gecode_component_found TRUE) - else() - set(_gecode_component_found FALSE) - endif() - set(Gecode_${_gecode_component}_FOUND ${_gecode_component_found}) - set(Gecode_gecode${_gecode_component}_FOUND ${_gecode_component_found}) -endforeach() - -foreach(_gecode_requested_component IN LISTS Gecode_FIND_COMPONENTS) - if(NOT DEFINED Gecode_${_gecode_requested_component}_FOUND) - set(Gecode_${_gecode_requested_component}_FOUND FALSE) - endif() -endforeach() +unset(_gecode_canonical_component) +unset(_gecode_built_components) unset(_gecode_component) +unset(_gecode_component_closure) +unset(_gecode_component_dependencies) +unset(_gecode_component_dependency) unset(_gecode_component_found) unset(_gecode_component_target) +unset(_gecode_dependency_component) +unset(_gecode_dependency_queue) +unset(_gecode_dependency_roots) +unset(_gecode_needs_qt) +unset(_gecode_package_prefix_dir) unset(_gecode_requested_component) unset(_gecode_supported_components) diff --git a/cmake/VerifyTestSelection.cmake b/cmake/VerifyTestSelection.cmake new file mode 100644 index 0000000000..6991a45e7d --- /dev/null +++ b/cmake/VerifyTestSelection.cmake @@ -0,0 +1,24 @@ +if(NOT DEFINED TEST_EXECUTABLE OR NOT DEFINED REQUIRED_TESTS) + message(FATAL_ERROR "TEST_EXECUTABLE and REQUIRED_TESTS are required") +endif() + +execute_process( + COMMAND "${TEST_EXECUTABLE}" -list + RESULT_VARIABLE list_result + OUTPUT_VARIABLE registered_tests + ERROR_VARIABLE list_error) +if(NOT list_result EQUAL 0) + message(FATAL_ERROR + "Could not list registered Gecode tests (${list_result}): ${list_error}") +endif() + +string(REPLACE "\r\n" "\n" registered_tests "${registered_tests}") +string(REPLACE "\r" "\n" registered_tests "${registered_tests}") +set(registered_tests "\n${registered_tests}\n") +foreach(required_test IN LISTS REQUIRED_TESTS) + string(FIND "${registered_tests}" "\n${required_test}\n" test_position) + if(test_position EQUAL -1) + message(FATAL_ERROR + "Required check test is not registered: ${required_test}") + endif() +endforeach() From fe3d5effc2161b7961a5b50be4e32083663d7590 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Fri, 10 Jul 2026 10:02:56 +0200 Subject: [PATCH 4/8] Restore Gist static macro compatibility Accept both the historical GIST_STATIC_LIBS spelling and the GECODE_GIST_STATIC_LIBS spelling in public headers. Cover both forms with preprocessing smoke tests. --- .github/ci/gist-static-macro-smoke.sh | 55 +++++++++++++++++++++++++++ gecode/gist.hh | 2 +- gecode/gist/qtgist.hh | 2 +- 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100755 .github/ci/gist-static-macro-smoke.sh diff --git a/.github/ci/gist-static-macro-smoke.sh b/.github/ci/gist-static-macro-smoke.sh new file mode 100755 index 0000000000..5207f674de --- /dev/null +++ b/.github/ci/gist-static-macro-smoke.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel)" +temp_root="${RUNNER_TEMP:-${TMPDIR:-/tmp}}" +work="$(mktemp -d "$temp_root/gecode-gist-static-macro.XXXXXX")" +trap 'rm -rf -- "$work"' EXIT + +mkdir -p "$work/stubs/gecode/gist" "$work/stubs/gecode" +touch \ + "$work/stubs/gecode/kernel.hh" \ + "$work/stubs/gecode/search.hh" \ + "$work/stubs/gecode/int.hh" \ + "$work/stubs/gecode/set.hh" \ + "$work/stubs/gecode/float.hh" \ + "$work/stubs/gecode/gist/treecanvas.hh" \ + "$work/stubs/gecode/gist/nodestats.hh" + +cxx="${CXX:-c++}" + +dump_macros() { + header=$1 + platform=$2 + shift 2 + "$cxx" -E -dM -x c++ \ + -I"$work/stubs" -I"$repo_root" \ + "$platform" -DGECODE_NO_AUTOLINK "$@" \ + -include "$header" /dev/null +} + +check_header() { + header=$1 + platform=$2 + + legacy="$(dump_macros "$header" "$platform" -DGIST_STATIC_LIBS)" + grep -Eq '^#define GECODE_GIST_EXPORT[[:space:]]*$' <<<"$legacy" + ! grep -Eq '^#define GECODE_STATIC_LIBS([[:space:]]|$)' <<<"$legacy" + + current="$(dump_macros "$header" "$platform" -DGECODE_STATIC_LIBS)" + grep -Eq '^#define GECODE_GIST_EXPORT[[:space:]]*$' <<<"$current" + ! grep -Eq '^#define GIST_STATIC_LIBS([[:space:]]|$)' <<<"$current" + + shared="$(dump_macros "$header" "$platform")" + grep -Eq '^#define GECODE_GIST_EXPORT __declspec\([[:space:]]*dllimport[[:space:]]*\)$' \ + <<<"$shared" + + building="$(dump_macros "$header" "$platform" -DGECODE_BUILD_GIST)" + grep -Eq '^#define GECODE_GIST_EXPORT __declspec\([[:space:]]*dllexport[[:space:]]*\)$' \ + <<<"$building" +} + +for header in gecode/gist.hh gecode/gist/qtgist.hh; do + check_header "$header" -D__MINGW32__ + check_header "$header" -D_MSC_VER=1930 +done diff --git a/gecode/gist.hh b/gecode/gist.hh index e4f5dd9f6a..73cd71f6d1 100644 --- a/gecode/gist.hh +++ b/gecode/gist.hh @@ -48,7 +48,7 @@ * */ -#if !defined(GECODE_STATIC_LIBS) && \ +#if !defined(GECODE_STATIC_LIBS) && !defined(GIST_STATIC_LIBS) && \ (defined(__CYGWIN__) || defined(__MINGW32__) || defined(_MSC_VER)) #ifdef GECODE_BUILD_GIST diff --git a/gecode/gist/qtgist.hh b/gecode/gist/qtgist.hh index 3cb90f2279..3099c37146 100644 --- a/gecode/gist/qtgist.hh +++ b/gecode/gist/qtgist.hh @@ -42,7 +42,7 @@ * */ -#if !defined(GECODE_STATIC_LIBS) && \ +#if !defined(GECODE_STATIC_LIBS) && !defined(GIST_STATIC_LIBS) && \ (defined(__CYGWIN__) || defined(__MINGW32__) || defined(_MSC_VER)) #ifdef GECODE_BUILD_GIST From 6150e200954b7e5abe0dadcbade524476cfd7e5c Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Fri, 10 Jul 2026 10:03:03 +0200 Subject: [PATCH 5/8] Make configure portable across POSIX shells Replace Bash-only substitutions and test operators in the Autoconf path. Run the custom VIS smoke test under dash and cover multiple VIS inputs. --- .github/ci/custom-vis-smoke.sh | 26 +++++++++++++++++++------- configure | 8 ++++---- configure.ac | 6 +++--- gecode.m4 | 2 +- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/.github/ci/custom-vis-smoke.sh b/.github/ci/custom-vis-smoke.sh index b9f0a85e4f..19dab14b8a 100644 --- a/.github/ci/custom-vis-smoke.sh +++ b/.github/ci/custom-vis-smoke.sh @@ -19,23 +19,35 @@ cd "$work" touch configure awk ' - /^Name:[[:space:]]*Int$/ { print "Name: Custom"; next } - /^Namespace:[[:space:]]*Gecode::Int$/ { print "Namespace: Gecode::Custom"; next } + /^Name:[[:space:]]*Int$/ { print "Name: CustomOne"; next } + /^Namespace:[[:space:]]*Gecode::Int$/ { print "Namespace: Gecode::CustomOne"; next } /^Ifdef:[[:space:]]*GECODE_HAS_INT_VARS$/ { next } { print } -' gecode/int/var-imp/int.vis > custom.vis +' gecode/int/var-imp/int.vis > custom-one.vis + +awk ' + /^Name:[[:space:]]*Int$/ { print "Name: CustomTwo"; next } + /^Namespace:[[:space:]]*Gecode::Int$/ { print "Namespace: Gecode::CustomTwo"; next } + /^Ifdef:[[:space:]]*GECODE_HAS_INT_VARS$/ { next } + { print } +' gecode/int/var-imp/int.vis > custom-two.vis CFLAGS="-fPIC" \ CXXFLAGS="-std=c++11 -fPIC -DGECODE_MEMORY_ALIGNMENT=16" \ - ./configure \ - --with-vis="$PWD/custom.vis" \ + dash ./configure \ + --with-vis="$PWD/custom-one.vis,$PWD/custom-two.vis" \ --disable-cpprofiler \ --disable-examples \ --disable-flatzinc \ --disable-float-vars \ --disable-gist \ - --disable-qt + --disable-qt 2>&1 | tee configure.log + +! grep -Fq "unexpected operator" configure.log +grep -Eq '^export DLLSUFFIX[[:space:]]*=[[:space:]]*\.' Makefile +grep -Fq "VIS = \$(top_srcdir)/gecode/int/var-imp/int.vis \$(top_srcdir)/gecode/int/var-imp/bool.vis \$(top_srcdir)/gecode/set/var-imp/set.vis \$(top_srcdir)/gecode/float/var-imp/float.vis $PWD/custom-one.vis $PWD/custom-two.vis" Makefile rm -f gecode/kernel/var-type.hpp gecode/kernel/var-imp.hpp make -j4 -grep -q "CustomVarImpConf" gecode/kernel/var-type.hpp +grep -q "CustomOneVarImpConf" gecode/kernel/var-type.hpp +grep -q "CustomTwoVarImpConf" gecode/kernel/var-type.hpp diff --git a/configure b/configure index 44c2067c27..e045291b3c 100755 --- a/configure +++ b/configure @@ -13043,7 +13043,7 @@ then : fi if test "${with_vis:-no}" != "no"; then - with_vis=${with_vis//,/ } + with_vis=`printf '%s\n' "$with_vis" | sed 's/,/ /g'` ac_gecode_vis="${with_vis}${ac_gecode_vis:+ }${ac_gecode_vis}" fi @@ -14546,9 +14546,9 @@ printf "%s\n" "#define GECODE_VERSION_NUMBER ${PACKAGE_VERSION_NUMBER}" >>confde ac_gecode_library_architecture=-${VERSION_DASHES}${ac_gecode_library_architecture} -if test "$ac_gecode_compiler_vendor" == "microsoft" \ - -o \( "$ac_gecode_compiler_vendor" == "intel" \ - -a "$gecode_host_os" == "windows" \) ; then +if test "$ac_gecode_compiler_vendor" = "microsoft" \ + -o \( "$ac_gecode_compiler_vendor" = "intel" \ + -a "$gecode_host_os" = "windows" \) ; then DLL_ARCH=${ac_gecode_library_architecture} else diff --git a/configure.ac b/configure.ac index de94afbba0..0f6ec71a5e 100644 --- a/configure.ac +++ b/configure.ac @@ -334,9 +334,9 @@ AC_DEFINE_UNQUOTED(GECODE_VERSION_NUMBER, [Gecode version]) ac_gecode_library_architecture=-${VERSION_DASHES}${ac_gecode_library_architecture} -if test "$ac_gecode_compiler_vendor" == "microsoft" \ - -o \( "$ac_gecode_compiler_vendor" == "intel" \ - -a "$gecode_host_os" == "windows" \) ; then +if test "$ac_gecode_compiler_vendor" = "microsoft" \ + -o \( "$ac_gecode_compiler_vendor" = "intel" \ + -a "$gecode_host_os" = "windows" \) ; then AC_SUBST(DLL_ARCH,[${ac_gecode_library_architecture}]) else AC_SUBST(DLL_ARCH,[""]) diff --git a/gecode.m4 b/gecode.m4 index 424f3c05b3..34aa92cf1d 100755 --- a/gecode.m4 +++ b/gecode.m4 @@ -343,7 +343,7 @@ AC_DEFUN([AC_GECODE_VIS],[ AS_HELP_STRING([--with-vis], [additional variable implementation specifications])) if test "${with_vis:-no}" != "no"; then - with_vis=${with_vis//,/ } + with_vis=`printf '%s\n' "$with_vis" | sed 's/,/ /g'` ac_gecode_vis="${with_vis}${ac_gecode_vis:+ }${ac_gecode_vis}" fi]) AC_DEFUN([AC_GECODE_VTI], From 35f10ea0b632c082fa5b4c7cb561fc018bb1983e Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Fri, 10 Jul 2026 10:03:08 +0200 Subject: [PATCH 6/8] Verify generated sources and test coverage in CI Keep CMake and Autoconf test selection aligned, run the new package and macro smoke checks, and reject stale generated sources using the release toolchain. Regenerate the FlatZinc parser with the pinned Bison version. --- .github/ci/generated-source-freshness.sh | 163 +++++++ .github/workflows/build.yml | 44 +- Makefile.in | 58 ++- gecode/flatzinc/parser.tab.cpp | 542 +++++++++++------------ gecode/flatzinc/parser.tab.hpp | 2 +- 5 files changed, 511 insertions(+), 298 deletions(-) create mode 100755 .github/ci/generated-source-freshness.sh diff --git a/.github/ci/generated-source-freshness.sh b/.github/ci/generated-source-freshness.sh new file mode 100755 index 0000000000..17e7ebb5f3 --- /dev/null +++ b/.github/ci/generated-source-freshness.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="${GECODE_SOURCE_ROOT:-$(git rev-parse --show-toplevel)}" +temp_root="${RUNNER_TEMP:-${TMPDIR:-/tmp}}" + +if [ ! -d "$temp_root" ]; then + echo "Temporary root is not a directory: $temp_root" >&2 + exit 1 +fi +if [ ! -d "$repo_root/.git" ] && [ ! -f "$repo_root/.git" ]; then + echo "Source root is not a Git worktree: $repo_root" >&2 + exit 1 +fi + +cleanup_root= +workspace= +pending_signal= +cleanup() { + if [ -n "$cleanup_root" ]; then + rm -rf -- "$cleanup_root" + fi +} +trap cleanup EXIT + +# Defer signals while ownership moves from an unowned candidate name to a +# successfully created directory. On collision, no cleanup path is recorded; +# on success, a pending signal is handled only after cleanup owns the path. +trap 'pending_signal=HUP' HUP +trap 'pending_signal=INT' INT +trap 'pending_signal=TERM' TERM + +umask 077 +for attempt in {1..100}; do + candidate="$temp_root/gecode-generated-check.${BASHPID:-$$}.$RANDOM.$RANDOM.$attempt" + if mkdir -m 700 "$candidate"; then + cleanup_root=$candidate + break + fi + if [ -n "$pending_signal" ]; then + exit 1 + fi +done +if [ -z "$cleanup_root" ]; then + echo "Could not create a private generated-file workspace below $temp_root" >&2 + exit 1 +fi +if [ -n "$pending_signal" ]; then + exit 1 +fi +trap 'exit 1' HUP INT TERM +workspace=$(mktemp -d "$cleanup_root/work.XXXXXX") +source_copy="$workspace/source" +mkdir "$source_copy" + +require_version() { + tool=$1 + expected=$2 + if ! command -v "$tool" >/dev/null 2>&1; then + echo "Required generator is unavailable: $tool ($expected required)" >&2 + exit 1 + fi + actual=$($tool --version | sed -n '1p') + if [ "$actual" != "$expected" ]; then + echo "Unsupported $tool version." >&2 + echo " required: $expected" >&2 + echo " found: $actual" >&2 + echo "Use the pinned generator version or regenerate and review all tracked outputs intentionally." >&2 + exit 1 + fi +} + +require_version autoconf "autoconf (GNU Autoconf) 2.72" +require_version bison "bison (GNU Bison) 3.8.2" +require_version flex "flex 2.6.4" +if ! command -v uv >/dev/null 2>&1; then + echo "Required generator runner is unavailable: uv" >&2 + exit 1 +fi + +# Copy the current tracked worktree content, including local tracked changes, +# without copying build products or allowing generators to touch the source. +git -C "$repo_root" ls-files -z | + tar -C "$repo_root" --null -T - -cf - | + tar -C "$source_copy" -xf - + +vis_files=( + ./gecode/int/var-imp/int.vis + ./gecode/int/var-imp/bool.vis + ./gecode/set/var-imp/set.vis + ./gecode/float/var-imp/float.vis +) + +normalize_generated_whitespace() { + output=$1 + awk ' + { + sub(/[ \t\r]+$/, "") + line[NR] = $0 + } + END { + last = NR + while ((last > 0) && (line[last] == "")) + last-- + for (i = 1; i <= last; i++) + print line[i] + } + ' "$output" > "$output.normalized" + mv "$output.normalized" "$output" +} + +( + cd "$source_copy" + # Ignore any autom4te cache so output matches a clean source release. + autoconf --force + # Autoconf 2.72 appends blank lines after the generated script body; source + # releases retain exactly one final newline. + normalize_generated_whitespace configure + uv run --script ./misc/genvarimp.py -typehpp "${vis_files[@]}" \ + > gecode/kernel/var-type.hpp + uv run --script ./misc/genvarimp.py -header "${vis_files[@]}" \ + > gecode/kernel/var-imp.hpp + bison -t -o gecode/flatzinc/parser.tab.cpp -d \ + ./gecode/flatzinc/parser.yxx + # Source releases keep generated Bison files free of whitespace-only lines + # and excess blank lines at EOF. Bison 3.8.2 preserves indentation from an + # otherwise blank grammar line and appends a blank line to its .cpp output. + for output in \ + gecode/flatzinc/parser.tab.cpp \ + gecode/flatzinc/parser.tab.hpp; do + normalize_generated_whitespace "$output" + done + flex -ogecode/flatzinc/lexer.yy.cpp ./gecode/flatzinc/lexer.lxx +) + +generated_files=( + configure + gecode/kernel/var-type.hpp + gecode/kernel/var-imp.hpp + gecode/flatzinc/parser.tab.cpp + gecode/flatzinc/parser.tab.hpp + gecode/flatzinc/lexer.yy.cpp +) + +stale=0 +for file in "${generated_files[@]}"; do + if cmp -s "$repo_root/$file" "$source_copy/$file"; then + continue + fi + stale=1 + echo "Generated source is stale: $file" >&2 + diff -u \ + --label "tracked/$file" \ + --label "regenerated/$file" \ + "$repo_root/$file" "$source_copy/$file" >&2 || true +done + +if [ "$stale" -ne 0 ]; then + echo "Regenerate with Autoconf 2.72, GNU Bison 3.8.2, GNU Flex 2.6.4, and uv, then commit the reviewed outputs." >&2 + exit 1 +fi + +echo "Generated source freshness check passed" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8360349c48..b5c5853a26 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,7 +51,21 @@ jobs: sudo apt-get update sudo apt-get install -y flex bison libgmp-dev libmpfr-dev - - name: Stabilize generated configure script timestamp + - name: Install generated-source tools + if: runner.os == 'macOS' + shell: bash + run: | + # The freshness script enforces exact versions and fails on formula drift. + brew install autoconf bison flex + echo "$(brew --prefix bison)/bin" >> "$GITHUB_PATH" + echo "$(brew --prefix flex)/bin" >> "$GITHUB_PATH" + + - name: Check generated source freshness + if: runner.os == 'macOS' + shell: bash + run: bash .github/ci/generated-source-freshness.sh + + - name: Avoid redundant configure regeneration during build shell: bash run: touch configure @@ -71,10 +85,6 @@ jobs: shell: bash run: bash .github/ci/autoconf-install-smoke.sh "$RUNNER_TEMP/gecode-autoconf-install" - - name: Generated Autoconf files stay clean - shell: bash - run: git diff --exit-code -- configure gecode/kernel/var-type.hpp gecode/kernel/var-imp.hpp - - name: Custom VIS dependency smoke if: runner.os == 'Linux' shell: bash @@ -136,7 +146,6 @@ jobs: run: | set -euxo pipefail cmake --build . --config $BUILD_TYPE --target check - ctest --output-on-failure -R '^test$' - name: Install CMake package working-directory: ${{github.workspace}}/build @@ -159,11 +168,25 @@ jobs: core fi + - name: Package version checks + shell: bash + run: bash .github/ci/cmake-package-version-checks.sh "$GITHUB_WORKSPACE/install" + - name: Disabled module closure checks if: matrix.os == 'ubuntu-latest' shell: bash run: bash .github/ci/cmake-closure-checks.sh + - name: CMake custom VIS checks + if: matrix.os == 'ubuntu-latest' + shell: bash + run: bash .github/ci/cmake-custom-vis-smoke.sh + + - name: Gist static macro compatibility + if: matrix.os == 'ubuntu-latest' + shell: bash + run: bash .github/ci/gist-static-macro-smoke.sh + - name: Qt minimum version checks if: matrix.os == 'ubuntu-latest' shell: bash @@ -202,10 +225,9 @@ jobs: - name: Check shell: bash - run: | - set -euxo pipefail - cmake --build "$GITHUB_WORKSPACE/build-ninja" --target check - ctest --test-dir "$GITHUB_WORKSPACE/build-ninja" --output-on-failure -R '^test$' + run: > + ctest --test-dir "$GITHUB_WORKSPACE/build-ninja" + --output-on-failure -R '^(build-gecode-test|test)$' - name: Configure CMake Debug audit smoke shell: bash @@ -497,7 +519,7 @@ jobs: export PATH="$UV_DIR:$PATH" uv --version - - name: Stabilize generated configure script timestamp + - name: Avoid redundant configure regeneration during build shell: msys2 {0} run: touch configure diff --git a/Makefile.in b/Makefile.in index b9140bf348..76b00aba16 100755 --- a/Makefile.in +++ b/Makefile.in @@ -1303,23 +1303,51 @@ compileexamples: $(EXAMPLEEXE) test: mkcompiledirs @$(MAKE) $(VARIMP) $(TESTEXE) +CHECKTESTS = Branch::Int::Dense::3 \ + FlatZinc::magic_square \ + Int::Arithmetic::Abs \ + Int::Arithmetic::ArgMax \ + Int::Arithmetic::Max::Nary \ + Int::Cumulative::Man::Fix::0::4 \ + Int::Distinct::Random \ + Int::Linear::Bool::Int::Lq \ + Int::MiniModel::LinExpr::Bool::352 \ + NoGoods::Queens \ + Search::DFS::Sol::Binary::Nary::Binary::1::1::1 \ + Set::Dom::Dom::Gr \ + Set::RelOp::ConstSSI::Union \ + Set::Sequence::SeqU1 \ + Set::Wait + +ifeq "@enable_float_vars@" "yes" +FLOATCHECKTESTS = Float::Arithmetic::PositiveNRootBounds \ + Float::Arithmetic::PowConsistency \ + Float::Arithmetic::MultZeroEndpoint \ + Float::Arithmetic::Pow::N::2::XY::Sol::C \ + Float::Arithmetic::NRoot::N::2::XY::Sol::C \ + Float::Arithmetic::Mult::XYZ::Sol::C +CHECKTESTS += $(FLOATCHECKTESTS) +REQUIREDFLOATCHECKTESTS = Float::Arithmetic::PositiveNRootBounds \ + Float::Arithmetic::PowConsistency \ + Float::Arithmetic::MultZeroEndpoint \ + Float::Arithmetic::Pow::N::2::XY::Sol::C \ + Float::Arithmetic::NRoot::N::2::XY::Sol::C \ + Float::Arithmetic::Mult::XYZ::Sol::C +endif + # A basic integrity test check: test - $(RUNENVIRONMENT) $(TESTEXE) -iter 2 -threads 0 -test Branch::Int::Dense::3 \ - -test FlatZinc::magic_square \ - -test Int::Arithmetic::Abs \ - -test Int::Arithmetic::ArgMax \ - -test Int::Arithmetic::Max::Nary \ - -test Int::Cumulative::Man::Fix::0::4 \ - -test Int::Distinct::Random \ - -test Int::Linear::Bool::Int::Lq \ - -test Int::MiniModel::LinExpr::Bool::352 \ - -test NoGoods::Queens \ - -test Search::DFS::Sol::Binary::Nary::Binary::1::1::1 \ - -test Set::Dom::Dom::Gr \ - -test Set::RelOp::ConstSSI::Union \ - -test Set::Sequence::SeqU1 \ - -test Set::Wait + @for t in $(REQUIREDFLOATCHECKTESTS); do \ + case " $(CHECKTESTS) " in *" $$t "*) ;; \ + *) echo "Required Float check test is missing: $$t" >&2; exit 1 ;; \ + esac; \ + if ! $(RUNENVIRONMENT) $(TESTEXE) -list | grep -Fqx "$$t"; then \ + echo "Required Float check test is not registered: $$t" >&2; \ + exit 1; \ + fi; \ + done + $(RUNENVIRONMENT) $(TESTEXE) -iter 2 -threads 0 -fixprob 1 \ + $(CHECKTESTS:%=-test %) ifeq "@top_srcdir@" "." mkcompiledirs: diff --git a/gecode/flatzinc/parser.tab.cpp b/gecode/flatzinc/parser.tab.cpp index 4a78bcece5..dd2ca07f3c 100644 --- a/gecode/flatzinc/parser.tab.cpp +++ b/gecode/flatzinc/parser.tab.cpp @@ -644,7 +644,7 @@ namespace Gecode { namespace FlatZinc { }} -#line 644 "gecode/flatzinc/parser.tab.cpp" +#line 648 "gecode/flatzinc/parser.tab.cpp" # ifndef YY_CAST # ifdef __cplusplus @@ -1188,23 +1188,23 @@ static const yytype_int8 yytranslate[] = /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ static const yytype_int16 yyrline[] = { - 0, 712, 712, 714, 716, 719, 720, 722, 724, 727, - 728, 730, 732, 735, 736, 743, 746, 748, 751, 752, - 755, 759, 760, 761, 762, 765, 767, 769, 770, 773, - 774, 777, 778, 784, 784, 787, 819, 851, 890, 923, - 932, 942, 951, 963, 1033, 1099, 1170, 1238, 1259, 1279, - 1299, 1322, 1326, 1341, 1365, 1366, 1370, 1372, 1375, 1375, - 1377, 1381, 1383, 1398, 1421, 1422, 1426, 1428, 1432, 1436, - 1438, 1453, 1476, 1477, 1481, 1483, 1486, 1489, 1491, 1506, - 1529, 1530, 1534, 1536, 1539, 1544, 1545, 1550, 1551, 1556, - 1557, 1562, 1563, 1567, 1724, 1738, 1763, 1765, 1767, 1773, - 1775, 1788, 1790, 1799, 1801, 1808, 1809, 1813, 1815, 1820, - 1821, 1825, 1827, 1832, 1833, 1837, 1839, 1844, 1845, 1849, - 1851, 1859, 1861, 1865, 1867, 1872, 1873, 1877, 1879, 1881, - 1883, 1885, 1981, 1996, 1997, 2001, 2003, 2011, 2045, 2052, - 2059, 2085, 2086, 2094, 2095, 2099, 2101, 2105, 2109, 2113, - 2115, 2119, 2121, 2123, 2126, 2126, 2129, 2131, 2133, 2135, - 2137, 2243, 2254 + 0, 716, 716, 718, 720, 723, 724, 726, 728, 731, + 732, 734, 736, 739, 740, 747, 750, 752, 755, 756, + 759, 763, 764, 765, 766, 769, 771, 773, 774, 777, + 778, 781, 782, 788, 788, 791, 823, 855, 894, 927, + 936, 946, 955, 967, 1037, 1103, 1174, 1242, 1263, 1283, + 1303, 1326, 1330, 1345, 1369, 1370, 1374, 1376, 1379, 1379, + 1381, 1385, 1387, 1402, 1425, 1426, 1430, 1432, 1436, 1440, + 1442, 1457, 1480, 1481, 1485, 1487, 1490, 1493, 1495, 1510, + 1533, 1534, 1538, 1540, 1543, 1548, 1549, 1554, 1555, 1560, + 1561, 1566, 1567, 1571, 1728, 1742, 1767, 1769, 1771, 1777, + 1779, 1792, 1794, 1803, 1805, 1812, 1813, 1817, 1819, 1824, + 1825, 1829, 1831, 1836, 1837, 1841, 1843, 1848, 1849, 1853, + 1855, 1863, 1865, 1869, 1871, 1876, 1877, 1881, 1883, 1885, + 1887, 1889, 1985, 2000, 2001, 2005, 2007, 2015, 2049, 2056, + 2063, 2089, 2090, 2098, 2099, 2103, 2105, 2109, 2113, 2117, + 2119, 2123, 2125, 2127, 2130, 2130, 2133, 2135, 2137, 2139, + 2141, 2247, 2258 }; #endif @@ -2286,31 +2286,31 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); switch (yyn) { case 15: /* preddecl_item: FZ_PREDICATE FZ_ID '(' pred_arg_list ')' */ -#line 744 "./gecode/flatzinc/parser.yxx" +#line 748 "./gecode/flatzinc/parser.yxx" { free((yyvsp[-3].sValue)); } -#line 2288 "gecode/flatzinc/parser.tab.cpp" +#line 2292 "gecode/flatzinc/parser.tab.cpp" break; case 20: /* pred_arg: pred_arg_type ':' FZ_ID */ -#line 756 "./gecode/flatzinc/parser.yxx" +#line 760 "./gecode/flatzinc/parser.yxx" { free((yyvsp[0].sValue)); } -#line 2294 "gecode/flatzinc/parser.tab.cpp" +#line 2298 "gecode/flatzinc/parser.tab.cpp" break; case 25: /* pred_arg_simple_type: int_ti_expr_tail */ -#line 766 "./gecode/flatzinc/parser.yxx" +#line 770 "./gecode/flatzinc/parser.yxx" { if ((yyvsp[0].oSet)()) delete (yyvsp[0].oSet).some(); } -#line 2300 "gecode/flatzinc/parser.tab.cpp" +#line 2304 "gecode/flatzinc/parser.tab.cpp" break; case 26: /* pred_arg_simple_type: FZ_SET FZ_OF int_ti_expr_tail */ -#line 768 "./gecode/flatzinc/parser.yxx" +#line 772 "./gecode/flatzinc/parser.yxx" { if ((yyvsp[0].oSet)()) delete (yyvsp[0].oSet).some(); } -#line 2306 "gecode/flatzinc/parser.tab.cpp" +#line 2310 "gecode/flatzinc/parser.tab.cpp" break; case 35: /* vardecl_item: FZ_VAR int_ti_expr_tail ':' var_par_id annotations non_array_expr_opt */ -#line 788 "./gecode/flatzinc/parser.yxx" +#line 792 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); bool print = (yyvsp[-1].argVec) != NULL && (yyvsp[-1].argVec)->hasAtom("output_var"); @@ -2342,11 +2342,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } delete (yyvsp[-1].argVec); free((yyvsp[-2].sValue)); } -#line 2342 "gecode/flatzinc/parser.tab.cpp" +#line 2346 "gecode/flatzinc/parser.tab.cpp" break; case 36: /* vardecl_item: FZ_VAR bool_ti_expr_tail ':' var_par_id annotations non_array_expr_opt */ -#line 820 "./gecode/flatzinc/parser.yxx" +#line 824 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); bool print = (yyvsp[-1].argVec) != NULL && (yyvsp[-1].argVec)->hasAtom("output_var"); @@ -2378,11 +2378,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } delete (yyvsp[-1].argVec); free((yyvsp[-2].sValue)); } -#line 2378 "gecode/flatzinc/parser.tab.cpp" +#line 2382 "gecode/flatzinc/parser.tab.cpp" break; case 37: /* vardecl_item: FZ_VAR float_ti_expr_tail ':' var_par_id annotations non_array_expr_opt */ -#line 852 "./gecode/flatzinc/parser.yxx" +#line 856 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); bool print = (yyvsp[-1].argVec) != NULL && (yyvsp[-1].argVec)->hasAtom("output_var"); @@ -2421,11 +2421,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } delete (yyvsp[-1].argVec); free((yyvsp[-2].sValue)); } -#line 2421 "gecode/flatzinc/parser.tab.cpp" +#line 2425 "gecode/flatzinc/parser.tab.cpp" break; case 38: /* vardecl_item: FZ_VAR FZ_SET FZ_OF int_ti_expr_tail ':' var_par_id annotations non_array_expr_opt */ -#line 891 "./gecode/flatzinc/parser.yxx" +#line 895 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); bool print = (yyvsp[-1].argVec) != NULL && (yyvsp[-1].argVec)->hasAtom("output_var"); @@ -2458,11 +2458,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } delete (yyvsp[-1].argVec); free((yyvsp[-2].sValue)); } -#line 2458 "gecode/flatzinc/parser.tab.cpp" +#line 2462 "gecode/flatzinc/parser.tab.cpp" break; case 39: /* vardecl_item: FZ_INT ':' var_par_id annotations '=' non_array_expr */ -#line 924 "./gecode/flatzinc/parser.yxx" +#line 928 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); yyassert(pp, (yyvsp[0].arg)->isInt(), "Invalid int initializer"); @@ -2471,11 +2471,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); "Duplicate symbol"); delete (yyvsp[-2].argVec); free((yyvsp[-3].sValue)); } -#line 2471 "gecode/flatzinc/parser.tab.cpp" +#line 2475 "gecode/flatzinc/parser.tab.cpp" break; case 40: /* vardecl_item: FZ_FLOAT ':' var_par_id annotations '=' non_array_expr */ -#line 933 "./gecode/flatzinc/parser.yxx" +#line 937 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); yyassert(pp, (yyvsp[0].arg)->isFloat(), "Invalid float initializer"); @@ -2485,11 +2485,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); "Duplicate symbol"); delete (yyvsp[-2].argVec); free((yyvsp[-3].sValue)); } -#line 2485 "gecode/flatzinc/parser.tab.cpp" +#line 2489 "gecode/flatzinc/parser.tab.cpp" break; case 41: /* vardecl_item: FZ_BOOL ':' var_par_id annotations '=' non_array_expr */ -#line 943 "./gecode/flatzinc/parser.yxx" +#line 947 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); yyassert(pp, (yyvsp[0].arg)->isBool(), "Invalid bool initializer"); @@ -2498,11 +2498,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); "Duplicate symbol"); delete (yyvsp[-2].argVec); free((yyvsp[-3].sValue)); } -#line 2498 "gecode/flatzinc/parser.tab.cpp" +#line 2502 "gecode/flatzinc/parser.tab.cpp" break; case 42: /* vardecl_item: FZ_SET FZ_OF FZ_INT ':' var_par_id annotations '=' non_array_expr */ -#line 952 "./gecode/flatzinc/parser.yxx" +#line 956 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); yyassert(pp, (yyvsp[0].arg)->isSet(), "Invalid set initializer"); @@ -2514,11 +2514,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); delete set; delete (yyvsp[-2].argVec); free((yyvsp[-3].sValue)); } -#line 2514 "gecode/flatzinc/parser.tab.cpp" +#line 2518 "gecode/flatzinc/parser.tab.cpp" break; case 43: /* vardecl_item: FZ_ARRAY '[' FZ_INT_LIT FZ_DOTDOT FZ_INT_LIT ']' FZ_OF FZ_VAR int_ti_expr_tail ':' var_par_id annotations vardecl_int_var_array_init */ -#line 965 "./gecode/flatzinc/parser.yxx" +#line 969 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); yyassert(pp, (yyvsp[-10].iValue)==1, "Arrays must start at 1"); @@ -2587,11 +2587,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } delete (yyvsp[-1].argVec); free((yyvsp[-2].sValue)); } -#line 2587 "gecode/flatzinc/parser.tab.cpp" +#line 2591 "gecode/flatzinc/parser.tab.cpp" break; case 44: /* vardecl_item: FZ_ARRAY '[' FZ_INT_LIT FZ_DOTDOT FZ_INT_LIT ']' FZ_OF FZ_VAR bool_ti_expr_tail ':' var_par_id annotations vardecl_bool_var_array_init */ -#line 1035 "./gecode/flatzinc/parser.yxx" +#line 1039 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); bool print = (yyvsp[-1].argVec) != NULL && (yyvsp[-1].argVec)->hasCall("output_array"); @@ -2656,11 +2656,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } delete (yyvsp[-1].argVec); free((yyvsp[-2].sValue)); } -#line 2656 "gecode/flatzinc/parser.tab.cpp" +#line 2660 "gecode/flatzinc/parser.tab.cpp" break; case 45: /* vardecl_item: FZ_ARRAY '[' FZ_INT_LIT FZ_DOTDOT FZ_INT_LIT ']' FZ_OF FZ_VAR float_ti_expr_tail ':' var_par_id annotations vardecl_float_var_array_init */ -#line 1102 "./gecode/flatzinc/parser.yxx" +#line 1106 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); yyassert(pp, (yyvsp[-10].iValue)==1, "Arrays must start at 1"); @@ -2729,11 +2729,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); if ((yyvsp[-4].oPFloat)()) delete (yyvsp[-4].oPFloat).some(); delete (yyvsp[-1].argVec); free((yyvsp[-2].sValue)); } -#line 2729 "gecode/flatzinc/parser.tab.cpp" +#line 2733 "gecode/flatzinc/parser.tab.cpp" break; case 46: /* vardecl_item: FZ_ARRAY '[' FZ_INT_LIT FZ_DOTDOT FZ_INT_LIT ']' FZ_OF FZ_VAR FZ_SET FZ_OF int_ti_expr_tail ':' var_par_id annotations vardecl_set_var_array_init */ -#line 1172 "./gecode/flatzinc/parser.yxx" +#line 1176 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); bool print = (yyvsp[-1].argVec) != NULL && (yyvsp[-1].argVec)->hasCall("output_array"); @@ -2800,11 +2800,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } delete (yyvsp[-1].argVec); free((yyvsp[-2].sValue)); } -#line 2800 "gecode/flatzinc/parser.tab.cpp" +#line 2804 "gecode/flatzinc/parser.tab.cpp" break; case 47: /* vardecl_item: FZ_ARRAY '[' FZ_INT_LIT FZ_DOTDOT FZ_INT_LIT ']' FZ_OF FZ_INT ':' var_par_id annotations '=' '[' int_list ']' */ -#line 1240 "./gecode/flatzinc/parser.yxx" +#line 1244 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); yyassert(pp, (yyvsp[-12].iValue)==1, "Arrays must start at 1"); @@ -2824,11 +2824,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); free((yyvsp[-5].sValue)); delete (yyvsp[-4].argVec); } -#line 2824 "gecode/flatzinc/parser.tab.cpp" +#line 2828 "gecode/flatzinc/parser.tab.cpp" break; case 48: /* vardecl_item: FZ_ARRAY '[' FZ_INT_LIT FZ_DOTDOT FZ_INT_LIT ']' FZ_OF FZ_BOOL ':' var_par_id annotations '=' '[' bool_list ']' */ -#line 1261 "./gecode/flatzinc/parser.yxx" +#line 1265 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); yyassert(pp, (yyvsp[-12].iValue)==1, "Arrays must start at 1"); @@ -2847,11 +2847,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); free((yyvsp[-5].sValue)); delete (yyvsp[-4].argVec); } -#line 2847 "gecode/flatzinc/parser.tab.cpp" +#line 2851 "gecode/flatzinc/parser.tab.cpp" break; case 49: /* vardecl_item: FZ_ARRAY '[' FZ_INT_LIT FZ_DOTDOT FZ_INT_LIT ']' FZ_OF FZ_FLOAT ':' var_par_id annotations '=' '[' float_list ']' */ -#line 1281 "./gecode/flatzinc/parser.yxx" +#line 1285 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); yyassert(pp, (yyvsp[-12].iValue)==1, "Arrays must start at 1"); @@ -2870,11 +2870,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); delete (yyvsp[-1].floatSetValue); delete (yyvsp[-4].argVec); free((yyvsp[-5].sValue)); } -#line 2870 "gecode/flatzinc/parser.tab.cpp" +#line 2874 "gecode/flatzinc/parser.tab.cpp" break; case 50: /* vardecl_item: FZ_ARRAY '[' FZ_INT_LIT FZ_DOTDOT FZ_INT_LIT ']' FZ_OF FZ_SET FZ_OF FZ_INT ':' var_par_id annotations '=' '[' set_literal_list ']' */ -#line 1301 "./gecode/flatzinc/parser.yxx" +#line 1305 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); yyassert(pp, (yyvsp[-14].iValue)==1, "Arrays must start at 1"); @@ -2894,19 +2894,19 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); delete (yyvsp[-1].setValueList); delete (yyvsp[-4].argVec); free((yyvsp[-5].sValue)); } -#line 2894 "gecode/flatzinc/parser.tab.cpp" +#line 2898 "gecode/flatzinc/parser.tab.cpp" break; case 51: /* int_init: FZ_INT_LIT */ -#line 1323 "./gecode/flatzinc/parser.yxx" +#line 1327 "./gecode/flatzinc/parser.yxx" { (yyval.varSpec) = new IntVarSpec((yyvsp[0].iValue),false,false); } -#line 2902 "gecode/flatzinc/parser.tab.cpp" +#line 2906 "gecode/flatzinc/parser.tab.cpp" break; case 52: /* int_init: var_par_id */ -#line 1327 "./gecode/flatzinc/parser.yxx" +#line 1331 "./gecode/flatzinc/parser.yxx" { SymbolEntry e; ParserState* pp = static_cast(parm); @@ -2921,11 +2921,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } free((yyvsp[0].sValue)); } -#line 2921 "gecode/flatzinc/parser.tab.cpp" +#line 2925 "gecode/flatzinc/parser.tab.cpp" break; case 53: /* int_init: var_par_id '[' FZ_INT_LIT ']' */ -#line 1342 "./gecode/flatzinc/parser.yxx" +#line 1346 "./gecode/flatzinc/parser.yxx" { vector v; SymbolEntry e; @@ -2946,47 +2946,47 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } free((yyvsp[-3].sValue)); } -#line 2946 "gecode/flatzinc/parser.tab.cpp" +#line 2950 "gecode/flatzinc/parser.tab.cpp" break; case 54: /* int_init_list: %empty */ -#line 1365 "./gecode/flatzinc/parser.yxx" +#line 1369 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = new vector(0); } -#line 2952 "gecode/flatzinc/parser.tab.cpp" +#line 2956 "gecode/flatzinc/parser.tab.cpp" break; case 55: /* int_init_list: int_init_list_head list_tail */ -#line 1367 "./gecode/flatzinc/parser.yxx" +#line 1371 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = (yyvsp[-1].varSpecVec); } -#line 2958 "gecode/flatzinc/parser.tab.cpp" +#line 2962 "gecode/flatzinc/parser.tab.cpp" break; case 56: /* int_init_list_head: int_init */ -#line 1371 "./gecode/flatzinc/parser.yxx" +#line 1375 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = new vector(1); (*(yyval.varSpecVec))[0] = (yyvsp[0].varSpec); } -#line 2964 "gecode/flatzinc/parser.tab.cpp" +#line 2968 "gecode/flatzinc/parser.tab.cpp" break; case 57: /* int_init_list_head: int_init_list_head ',' int_init */ -#line 1373 "./gecode/flatzinc/parser.yxx" +#line 1377 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = (yyvsp[-2].varSpecVec); (yyval.varSpecVec)->push_back((yyvsp[0].varSpec)); } -#line 2970 "gecode/flatzinc/parser.tab.cpp" +#line 2974 "gecode/flatzinc/parser.tab.cpp" break; case 60: /* int_var_array_literal: '[' int_init_list ']' */ -#line 1378 "./gecode/flatzinc/parser.yxx" +#line 1382 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = (yyvsp[-1].varSpecVec); } -#line 2976 "gecode/flatzinc/parser.tab.cpp" +#line 2980 "gecode/flatzinc/parser.tab.cpp" break; case 61: /* float_init: FZ_FLOAT_LIT */ -#line 1382 "./gecode/flatzinc/parser.yxx" +#line 1386 "./gecode/flatzinc/parser.yxx" { (yyval.varSpec) = new FloatVarSpec((yyvsp[0].dValue),false,false); } -#line 2982 "gecode/flatzinc/parser.tab.cpp" +#line 2986 "gecode/flatzinc/parser.tab.cpp" break; case 62: /* float_init: var_par_id */ -#line 1384 "./gecode/flatzinc/parser.yxx" +#line 1388 "./gecode/flatzinc/parser.yxx" { SymbolEntry e; ParserState* pp = static_cast(parm); @@ -3001,11 +3001,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } free((yyvsp[0].sValue)); } -#line 3001 "gecode/flatzinc/parser.tab.cpp" +#line 3005 "gecode/flatzinc/parser.tab.cpp" break; case 63: /* float_init: var_par_id '[' FZ_INT_LIT ']' */ -#line 1399 "./gecode/flatzinc/parser.yxx" +#line 1403 "./gecode/flatzinc/parser.yxx" { SymbolEntry e; ParserState* pp = static_cast(parm); @@ -3025,47 +3025,47 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } free((yyvsp[-3].sValue)); } -#line 3025 "gecode/flatzinc/parser.tab.cpp" +#line 3029 "gecode/flatzinc/parser.tab.cpp" break; case 64: /* float_init_list: %empty */ -#line 1421 "./gecode/flatzinc/parser.yxx" +#line 1425 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = new vector(0); } -#line 3031 "gecode/flatzinc/parser.tab.cpp" +#line 3035 "gecode/flatzinc/parser.tab.cpp" break; case 65: /* float_init_list: float_init_list_head list_tail */ -#line 1423 "./gecode/flatzinc/parser.yxx" +#line 1427 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = (yyvsp[-1].varSpecVec); } -#line 3037 "gecode/flatzinc/parser.tab.cpp" +#line 3041 "gecode/flatzinc/parser.tab.cpp" break; case 66: /* float_init_list_head: float_init */ -#line 1427 "./gecode/flatzinc/parser.yxx" +#line 1431 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = new vector(1); (*(yyval.varSpecVec))[0] = (yyvsp[0].varSpec); } -#line 3043 "gecode/flatzinc/parser.tab.cpp" +#line 3047 "gecode/flatzinc/parser.tab.cpp" break; case 67: /* float_init_list_head: float_init_list_head ',' float_init */ -#line 1429 "./gecode/flatzinc/parser.yxx" +#line 1433 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = (yyvsp[-2].varSpecVec); (yyval.varSpecVec)->push_back((yyvsp[0].varSpec)); } -#line 3049 "gecode/flatzinc/parser.tab.cpp" +#line 3053 "gecode/flatzinc/parser.tab.cpp" break; case 68: /* float_var_array_literal: '[' float_init_list ']' */ -#line 1433 "./gecode/flatzinc/parser.yxx" +#line 1437 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = (yyvsp[-1].varSpecVec); } -#line 3055 "gecode/flatzinc/parser.tab.cpp" +#line 3059 "gecode/flatzinc/parser.tab.cpp" break; case 69: /* bool_init: FZ_BOOL_LIT */ -#line 1437 "./gecode/flatzinc/parser.yxx" +#line 1441 "./gecode/flatzinc/parser.yxx" { (yyval.varSpec) = new BoolVarSpec((yyvsp[0].iValue),false,false); } -#line 3061 "gecode/flatzinc/parser.tab.cpp" +#line 3065 "gecode/flatzinc/parser.tab.cpp" break; case 70: /* bool_init: var_par_id */ -#line 1439 "./gecode/flatzinc/parser.yxx" +#line 1443 "./gecode/flatzinc/parser.yxx" { SymbolEntry e; ParserState* pp = static_cast(parm); @@ -3080,11 +3080,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } free((yyvsp[0].sValue)); } -#line 3080 "gecode/flatzinc/parser.tab.cpp" +#line 3084 "gecode/flatzinc/parser.tab.cpp" break; case 71: /* bool_init: var_par_id '[' FZ_INT_LIT ']' */ -#line 1454 "./gecode/flatzinc/parser.yxx" +#line 1458 "./gecode/flatzinc/parser.yxx" { SymbolEntry e; ParserState* pp = static_cast(parm); @@ -3104,47 +3104,47 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } free((yyvsp[-3].sValue)); } -#line 3104 "gecode/flatzinc/parser.tab.cpp" +#line 3108 "gecode/flatzinc/parser.tab.cpp" break; case 72: /* bool_init_list: %empty */ -#line 1476 "./gecode/flatzinc/parser.yxx" +#line 1480 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = new vector(0); } -#line 3110 "gecode/flatzinc/parser.tab.cpp" +#line 3114 "gecode/flatzinc/parser.tab.cpp" break; case 73: /* bool_init_list: bool_init_list_head list_tail */ -#line 1478 "./gecode/flatzinc/parser.yxx" +#line 1482 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = (yyvsp[-1].varSpecVec); } -#line 3116 "gecode/flatzinc/parser.tab.cpp" +#line 3120 "gecode/flatzinc/parser.tab.cpp" break; case 74: /* bool_init_list_head: bool_init */ -#line 1482 "./gecode/flatzinc/parser.yxx" +#line 1486 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = new vector(1); (*(yyval.varSpecVec))[0] = (yyvsp[0].varSpec); } -#line 3122 "gecode/flatzinc/parser.tab.cpp" +#line 3126 "gecode/flatzinc/parser.tab.cpp" break; case 75: /* bool_init_list_head: bool_init_list_head ',' bool_init */ -#line 1484 "./gecode/flatzinc/parser.yxx" +#line 1488 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = (yyvsp[-2].varSpecVec); (yyval.varSpecVec)->push_back((yyvsp[0].varSpec)); } -#line 3128 "gecode/flatzinc/parser.tab.cpp" +#line 3132 "gecode/flatzinc/parser.tab.cpp" break; case 76: /* bool_var_array_literal: '[' bool_init_list ']' */ -#line 1486 "./gecode/flatzinc/parser.yxx" +#line 1490 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = (yyvsp[-1].varSpecVec); } -#line 3134 "gecode/flatzinc/parser.tab.cpp" +#line 3138 "gecode/flatzinc/parser.tab.cpp" break; case 77: /* set_init: set_literal */ -#line 1490 "./gecode/flatzinc/parser.yxx" +#line 1494 "./gecode/flatzinc/parser.yxx" { (yyval.varSpec) = new SetVarSpec((yyvsp[0].setLit),false,false); } -#line 3140 "gecode/flatzinc/parser.tab.cpp" +#line 3144 "gecode/flatzinc/parser.tab.cpp" break; case 78: /* set_init: var_par_id */ -#line 1492 "./gecode/flatzinc/parser.yxx" +#line 1496 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); SymbolEntry e; @@ -3159,11 +3159,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } free((yyvsp[0].sValue)); } -#line 3159 "gecode/flatzinc/parser.tab.cpp" +#line 3163 "gecode/flatzinc/parser.tab.cpp" break; case 79: /* set_init: var_par_id '[' FZ_INT_LIT ']' */ -#line 1507 "./gecode/flatzinc/parser.yxx" +#line 1511 "./gecode/flatzinc/parser.yxx" { SymbolEntry e; ParserState* pp = static_cast(parm); @@ -3183,89 +3183,89 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } free((yyvsp[-3].sValue)); } -#line 3183 "gecode/flatzinc/parser.tab.cpp" +#line 3187 "gecode/flatzinc/parser.tab.cpp" break; case 80: /* set_init_list: %empty */ -#line 1529 "./gecode/flatzinc/parser.yxx" +#line 1533 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = new vector(0); } -#line 3189 "gecode/flatzinc/parser.tab.cpp" +#line 3193 "gecode/flatzinc/parser.tab.cpp" break; case 81: /* set_init_list: set_init_list_head list_tail */ -#line 1531 "./gecode/flatzinc/parser.yxx" +#line 1535 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = (yyvsp[-1].varSpecVec); } -#line 3195 "gecode/flatzinc/parser.tab.cpp" +#line 3199 "gecode/flatzinc/parser.tab.cpp" break; case 82: /* set_init_list_head: set_init */ -#line 1535 "./gecode/flatzinc/parser.yxx" +#line 1539 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = new vector(1); (*(yyval.varSpecVec))[0] = (yyvsp[0].varSpec); } -#line 3201 "gecode/flatzinc/parser.tab.cpp" +#line 3205 "gecode/flatzinc/parser.tab.cpp" break; case 83: /* set_init_list_head: set_init_list_head ',' set_init */ -#line 1537 "./gecode/flatzinc/parser.yxx" +#line 1541 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = (yyvsp[-2].varSpecVec); (yyval.varSpecVec)->push_back((yyvsp[0].varSpec)); } -#line 3207 "gecode/flatzinc/parser.tab.cpp" +#line 3211 "gecode/flatzinc/parser.tab.cpp" break; case 84: /* set_var_array_literal: '[' set_init_list ']' */ -#line 1540 "./gecode/flatzinc/parser.yxx" +#line 1544 "./gecode/flatzinc/parser.yxx" { (yyval.varSpecVec) = (yyvsp[-1].varSpecVec); } -#line 3213 "gecode/flatzinc/parser.tab.cpp" +#line 3217 "gecode/flatzinc/parser.tab.cpp" break; case 85: /* vardecl_int_var_array_init: %empty */ -#line 1544 "./gecode/flatzinc/parser.yxx" +#line 1548 "./gecode/flatzinc/parser.yxx" { (yyval.oVarSpecVec) = Option* >::none(); } -#line 3219 "gecode/flatzinc/parser.tab.cpp" +#line 3223 "gecode/flatzinc/parser.tab.cpp" break; case 86: /* vardecl_int_var_array_init: '=' int_var_array_literal */ -#line 1546 "./gecode/flatzinc/parser.yxx" +#line 1550 "./gecode/flatzinc/parser.yxx" { (yyval.oVarSpecVec) = Option* >::some((yyvsp[0].varSpecVec)); } -#line 3225 "gecode/flatzinc/parser.tab.cpp" +#line 3229 "gecode/flatzinc/parser.tab.cpp" break; case 87: /* vardecl_bool_var_array_init: %empty */ -#line 1550 "./gecode/flatzinc/parser.yxx" +#line 1554 "./gecode/flatzinc/parser.yxx" { (yyval.oVarSpecVec) = Option* >::none(); } -#line 3231 "gecode/flatzinc/parser.tab.cpp" +#line 3235 "gecode/flatzinc/parser.tab.cpp" break; case 88: /* vardecl_bool_var_array_init: '=' bool_var_array_literal */ -#line 1552 "./gecode/flatzinc/parser.yxx" +#line 1556 "./gecode/flatzinc/parser.yxx" { (yyval.oVarSpecVec) = Option* >::some((yyvsp[0].varSpecVec)); } -#line 3237 "gecode/flatzinc/parser.tab.cpp" +#line 3241 "gecode/flatzinc/parser.tab.cpp" break; case 89: /* vardecl_float_var_array_init: %empty */ -#line 1556 "./gecode/flatzinc/parser.yxx" +#line 1560 "./gecode/flatzinc/parser.yxx" { (yyval.oVarSpecVec) = Option* >::none(); } -#line 3243 "gecode/flatzinc/parser.tab.cpp" +#line 3247 "gecode/flatzinc/parser.tab.cpp" break; case 90: /* vardecl_float_var_array_init: '=' float_var_array_literal */ -#line 1558 "./gecode/flatzinc/parser.yxx" +#line 1562 "./gecode/flatzinc/parser.yxx" { (yyval.oVarSpecVec) = Option* >::some((yyvsp[0].varSpecVec)); } -#line 3249 "gecode/flatzinc/parser.tab.cpp" +#line 3253 "gecode/flatzinc/parser.tab.cpp" break; case 91: /* vardecl_set_var_array_init: %empty */ -#line 1562 "./gecode/flatzinc/parser.yxx" +#line 1566 "./gecode/flatzinc/parser.yxx" { (yyval.oVarSpecVec) = Option* >::none(); } -#line 3255 "gecode/flatzinc/parser.tab.cpp" +#line 3259 "gecode/flatzinc/parser.tab.cpp" break; case 92: /* vardecl_set_var_array_init: '=' set_var_array_literal */ -#line 1564 "./gecode/flatzinc/parser.yxx" +#line 1568 "./gecode/flatzinc/parser.yxx" { (yyval.oVarSpecVec) = Option* >::some((yyvsp[0].varSpecVec)); } -#line 3261 "gecode/flatzinc/parser.tab.cpp" +#line 3265 "gecode/flatzinc/parser.tab.cpp" break; case 93: /* constraint_item: FZ_CONSTRAINT FZ_ID '(' flat_expr_list ')' annotations */ -#line 1568 "./gecode/flatzinc/parser.yxx" +#line 1572 "./gecode/flatzinc/parser.yxx" { ParserState *pp = static_cast(parm); if (!pp->hadError) { @@ -3421,11 +3421,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } free((yyvsp[-4].sValue)); } -#line 3421 "gecode/flatzinc/parser.tab.cpp" +#line 3425 "gecode/flatzinc/parser.tab.cpp" break; case 94: /* solve_item: FZ_SOLVE annotations FZ_SATISFY */ -#line 1725 "./gecode/flatzinc/parser.yxx" +#line 1729 "./gecode/flatzinc/parser.yxx" { ParserState *pp = static_cast(parm); initfg(pp); @@ -3439,11 +3439,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); delete (yyvsp[-1].argVec); } } -#line 3439 "gecode/flatzinc/parser.tab.cpp" +#line 3443 "gecode/flatzinc/parser.tab.cpp" break; case 95: /* solve_item: FZ_SOLVE annotations minmax solve_expr */ -#line 1739 "./gecode/flatzinc/parser.yxx" +#line 1743 "./gecode/flatzinc/parser.yxx" { ParserState *pp = static_cast(parm); initfg(pp); @@ -3462,37 +3462,37 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); delete (yyvsp[-2].argVec); } } -#line 3462 "gecode/flatzinc/parser.tab.cpp" +#line 3466 "gecode/flatzinc/parser.tab.cpp" break; case 96: /* int_ti_expr_tail: FZ_INT */ -#line 1764 "./gecode/flatzinc/parser.yxx" +#line 1768 "./gecode/flatzinc/parser.yxx" { (yyval.oSet) = Option::none(); } -#line 3468 "gecode/flatzinc/parser.tab.cpp" +#line 3472 "gecode/flatzinc/parser.tab.cpp" break; case 97: /* int_ti_expr_tail: '{' int_list '}' */ -#line 1766 "./gecode/flatzinc/parser.yxx" +#line 1770 "./gecode/flatzinc/parser.yxx" { (yyval.oSet) = Option::some(new AST::SetLit(*(yyvsp[-1].setValue))); } -#line 3474 "gecode/flatzinc/parser.tab.cpp" +#line 3478 "gecode/flatzinc/parser.tab.cpp" break; case 98: /* int_ti_expr_tail: FZ_INT_LIT FZ_DOTDOT FZ_INT_LIT */ -#line 1768 "./gecode/flatzinc/parser.yxx" +#line 1772 "./gecode/flatzinc/parser.yxx" { (yyval.oSet) = Option::some(new AST::SetLit((yyvsp[-2].iValue), (yyvsp[0].iValue))); } -#line 3482 "gecode/flatzinc/parser.tab.cpp" +#line 3486 "gecode/flatzinc/parser.tab.cpp" break; case 99: /* bool_ti_expr_tail: FZ_BOOL */ -#line 1774 "./gecode/flatzinc/parser.yxx" +#line 1778 "./gecode/flatzinc/parser.yxx" { (yyval.oSet) = Option::none(); } -#line 3488 "gecode/flatzinc/parser.tab.cpp" +#line 3492 "gecode/flatzinc/parser.tab.cpp" break; case 100: /* bool_ti_expr_tail: '{' bool_list_head list_tail '}' */ -#line 1776 "./gecode/flatzinc/parser.yxx" +#line 1780 "./gecode/flatzinc/parser.yxx" { bool haveTrue = false; bool haveFalse = false; for (int i=(yyvsp[-2].setValue)->size(); i--;) { @@ -3503,192 +3503,192 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); (yyval.oSet) = Option::some( new AST::SetLit(!haveFalse,haveTrue)); } -#line 3503 "gecode/flatzinc/parser.tab.cpp" +#line 3507 "gecode/flatzinc/parser.tab.cpp" break; case 101: /* float_ti_expr_tail: FZ_FLOAT */ -#line 1789 "./gecode/flatzinc/parser.yxx" +#line 1793 "./gecode/flatzinc/parser.yxx" { (yyval.oPFloat) = Option* >::none(); } -#line 3509 "gecode/flatzinc/parser.tab.cpp" +#line 3513 "gecode/flatzinc/parser.tab.cpp" break; case 102: /* float_ti_expr_tail: FZ_FLOAT_LIT FZ_DOTDOT FZ_FLOAT_LIT */ -#line 1791 "./gecode/flatzinc/parser.yxx" +#line 1795 "./gecode/flatzinc/parser.yxx" { std::pair* dom = new std::pair((yyvsp[-2].dValue),(yyvsp[0].dValue)); (yyval.oPFloat) = Option* >::some(dom); } -#line 3516 "gecode/flatzinc/parser.tab.cpp" +#line 3520 "gecode/flatzinc/parser.tab.cpp" break; case 103: /* set_literal: '{' int_list '}' */ -#line 1800 "./gecode/flatzinc/parser.yxx" +#line 1804 "./gecode/flatzinc/parser.yxx" { (yyval.setLit) = new AST::SetLit(*(yyvsp[-1].setValue)); } -#line 3522 "gecode/flatzinc/parser.tab.cpp" +#line 3526 "gecode/flatzinc/parser.tab.cpp" break; case 104: /* set_literal: FZ_INT_LIT FZ_DOTDOT FZ_INT_LIT */ -#line 1802 "./gecode/flatzinc/parser.yxx" +#line 1806 "./gecode/flatzinc/parser.yxx" { (yyval.setLit) = new AST::SetLit((yyvsp[-2].iValue), (yyvsp[0].iValue)); } -#line 3528 "gecode/flatzinc/parser.tab.cpp" +#line 3532 "gecode/flatzinc/parser.tab.cpp" break; case 105: /* int_list: %empty */ -#line 1808 "./gecode/flatzinc/parser.yxx" +#line 1812 "./gecode/flatzinc/parser.yxx" { (yyval.setValue) = new vector(0); } -#line 3534 "gecode/flatzinc/parser.tab.cpp" +#line 3538 "gecode/flatzinc/parser.tab.cpp" break; case 106: /* int_list: int_list_head list_tail */ -#line 1810 "./gecode/flatzinc/parser.yxx" +#line 1814 "./gecode/flatzinc/parser.yxx" { (yyval.setValue) = (yyvsp[-1].setValue); } -#line 3540 "gecode/flatzinc/parser.tab.cpp" +#line 3544 "gecode/flatzinc/parser.tab.cpp" break; case 107: /* int_list_head: FZ_INT_LIT */ -#line 1814 "./gecode/flatzinc/parser.yxx" +#line 1818 "./gecode/flatzinc/parser.yxx" { (yyval.setValue) = new vector(1); (*(yyval.setValue))[0] = (yyvsp[0].iValue); } -#line 3546 "gecode/flatzinc/parser.tab.cpp" +#line 3550 "gecode/flatzinc/parser.tab.cpp" break; case 108: /* int_list_head: int_list_head ',' FZ_INT_LIT */ -#line 1816 "./gecode/flatzinc/parser.yxx" +#line 1820 "./gecode/flatzinc/parser.yxx" { (yyval.setValue) = (yyvsp[-2].setValue); (yyval.setValue)->push_back((yyvsp[0].iValue)); } -#line 3552 "gecode/flatzinc/parser.tab.cpp" +#line 3556 "gecode/flatzinc/parser.tab.cpp" break; case 109: /* bool_list: %empty */ -#line 1820 "./gecode/flatzinc/parser.yxx" +#line 1824 "./gecode/flatzinc/parser.yxx" { (yyval.setValue) = new vector(0); } -#line 3558 "gecode/flatzinc/parser.tab.cpp" +#line 3562 "gecode/flatzinc/parser.tab.cpp" break; case 110: /* bool_list: bool_list_head list_tail */ -#line 1822 "./gecode/flatzinc/parser.yxx" +#line 1826 "./gecode/flatzinc/parser.yxx" { (yyval.setValue) = (yyvsp[-1].setValue); } -#line 3564 "gecode/flatzinc/parser.tab.cpp" +#line 3568 "gecode/flatzinc/parser.tab.cpp" break; case 111: /* bool_list_head: FZ_BOOL_LIT */ -#line 1826 "./gecode/flatzinc/parser.yxx" +#line 1830 "./gecode/flatzinc/parser.yxx" { (yyval.setValue) = new vector(1); (*(yyval.setValue))[0] = (yyvsp[0].iValue); } -#line 3570 "gecode/flatzinc/parser.tab.cpp" +#line 3574 "gecode/flatzinc/parser.tab.cpp" break; case 112: /* bool_list_head: bool_list_head ',' FZ_BOOL_LIT */ -#line 1828 "./gecode/flatzinc/parser.yxx" +#line 1832 "./gecode/flatzinc/parser.yxx" { (yyval.setValue) = (yyvsp[-2].setValue); (yyval.setValue)->push_back((yyvsp[0].iValue)); } -#line 3576 "gecode/flatzinc/parser.tab.cpp" +#line 3580 "gecode/flatzinc/parser.tab.cpp" break; case 113: /* float_list: %empty */ -#line 1832 "./gecode/flatzinc/parser.yxx" +#line 1836 "./gecode/flatzinc/parser.yxx" { (yyval.floatSetValue) = new vector(0); } -#line 3582 "gecode/flatzinc/parser.tab.cpp" +#line 3586 "gecode/flatzinc/parser.tab.cpp" break; case 114: /* float_list: float_list_head list_tail */ -#line 1834 "./gecode/flatzinc/parser.yxx" +#line 1838 "./gecode/flatzinc/parser.yxx" { (yyval.floatSetValue) = (yyvsp[-1].floatSetValue); } -#line 3588 "gecode/flatzinc/parser.tab.cpp" +#line 3592 "gecode/flatzinc/parser.tab.cpp" break; case 115: /* float_list_head: FZ_FLOAT_LIT */ -#line 1838 "./gecode/flatzinc/parser.yxx" +#line 1842 "./gecode/flatzinc/parser.yxx" { (yyval.floatSetValue) = new vector(1); (*(yyval.floatSetValue))[0] = (yyvsp[0].dValue); } -#line 3594 "gecode/flatzinc/parser.tab.cpp" +#line 3598 "gecode/flatzinc/parser.tab.cpp" break; case 116: /* float_list_head: float_list_head ',' FZ_FLOAT_LIT */ -#line 1840 "./gecode/flatzinc/parser.yxx" +#line 1844 "./gecode/flatzinc/parser.yxx" { (yyval.floatSetValue) = (yyvsp[-2].floatSetValue); (yyval.floatSetValue)->push_back((yyvsp[0].dValue)); } -#line 3600 "gecode/flatzinc/parser.tab.cpp" +#line 3604 "gecode/flatzinc/parser.tab.cpp" break; case 117: /* set_literal_list: %empty */ -#line 1844 "./gecode/flatzinc/parser.yxx" +#line 1848 "./gecode/flatzinc/parser.yxx" { (yyval.setValueList) = new vector(0); } -#line 3606 "gecode/flatzinc/parser.tab.cpp" +#line 3610 "gecode/flatzinc/parser.tab.cpp" break; case 118: /* set_literal_list: set_literal_list_head list_tail */ -#line 1846 "./gecode/flatzinc/parser.yxx" +#line 1850 "./gecode/flatzinc/parser.yxx" { (yyval.setValueList) = (yyvsp[-1].setValueList); } -#line 3612 "gecode/flatzinc/parser.tab.cpp" +#line 3616 "gecode/flatzinc/parser.tab.cpp" break; case 119: /* set_literal_list_head: set_literal */ -#line 1850 "./gecode/flatzinc/parser.yxx" +#line 1854 "./gecode/flatzinc/parser.yxx" { (yyval.setValueList) = new vector(1); (*(yyval.setValueList))[0] = *(yyvsp[0].setLit); delete (yyvsp[0].setLit); } -#line 3618 "gecode/flatzinc/parser.tab.cpp" +#line 3622 "gecode/flatzinc/parser.tab.cpp" break; case 120: /* set_literal_list_head: set_literal_list_head ',' set_literal */ -#line 1852 "./gecode/flatzinc/parser.yxx" +#line 1856 "./gecode/flatzinc/parser.yxx" { (yyval.setValueList) = (yyvsp[-2].setValueList); (yyval.setValueList)->push_back(*(yyvsp[0].setLit)); delete (yyvsp[0].setLit); } -#line 3624 "gecode/flatzinc/parser.tab.cpp" +#line 3628 "gecode/flatzinc/parser.tab.cpp" break; case 121: /* flat_expr_list: flat_expr */ -#line 1860 "./gecode/flatzinc/parser.yxx" +#line 1864 "./gecode/flatzinc/parser.yxx" { (yyval.argVec) = new AST::Array((yyvsp[0].arg)); } -#line 3630 "gecode/flatzinc/parser.tab.cpp" +#line 3634 "gecode/flatzinc/parser.tab.cpp" break; case 122: /* flat_expr_list: flat_expr_list ',' flat_expr */ -#line 1862 "./gecode/flatzinc/parser.yxx" +#line 1866 "./gecode/flatzinc/parser.yxx" { (yyval.argVec) = (yyvsp[-2].argVec); (yyval.argVec)->append((yyvsp[0].arg)); } -#line 3636 "gecode/flatzinc/parser.tab.cpp" +#line 3640 "gecode/flatzinc/parser.tab.cpp" break; case 123: /* flat_expr: non_array_expr */ -#line 1866 "./gecode/flatzinc/parser.yxx" +#line 1870 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = (yyvsp[0].arg); } -#line 3642 "gecode/flatzinc/parser.tab.cpp" +#line 3646 "gecode/flatzinc/parser.tab.cpp" break; case 124: /* flat_expr: '[' non_array_expr_list ']' */ -#line 1868 "./gecode/flatzinc/parser.yxx" +#line 1872 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = (yyvsp[-1].argVec); } -#line 3648 "gecode/flatzinc/parser.tab.cpp" +#line 3652 "gecode/flatzinc/parser.tab.cpp" break; case 125: /* non_array_expr_opt: %empty */ -#line 1872 "./gecode/flatzinc/parser.yxx" +#line 1876 "./gecode/flatzinc/parser.yxx" { (yyval.oArg) = Option::none(); } -#line 3654 "gecode/flatzinc/parser.tab.cpp" +#line 3658 "gecode/flatzinc/parser.tab.cpp" break; case 126: /* non_array_expr_opt: '=' non_array_expr */ -#line 1874 "./gecode/flatzinc/parser.yxx" +#line 1878 "./gecode/flatzinc/parser.yxx" { (yyval.oArg) = Option::some((yyvsp[0].arg)); } -#line 3660 "gecode/flatzinc/parser.tab.cpp" +#line 3664 "gecode/flatzinc/parser.tab.cpp" break; case 127: /* non_array_expr: FZ_BOOL_LIT */ -#line 1878 "./gecode/flatzinc/parser.yxx" +#line 1882 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = new AST::BoolLit((yyvsp[0].iValue)); } -#line 3666 "gecode/flatzinc/parser.tab.cpp" +#line 3670 "gecode/flatzinc/parser.tab.cpp" break; case 128: /* non_array_expr: FZ_INT_LIT */ -#line 1880 "./gecode/flatzinc/parser.yxx" +#line 1884 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = new AST::IntLit((yyvsp[0].iValue)); } -#line 3672 "gecode/flatzinc/parser.tab.cpp" +#line 3676 "gecode/flatzinc/parser.tab.cpp" break; case 129: /* non_array_expr: FZ_FLOAT_LIT */ -#line 1882 "./gecode/flatzinc/parser.yxx" +#line 1886 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = new AST::FloatLit((yyvsp[0].dValue)); } -#line 3678 "gecode/flatzinc/parser.tab.cpp" +#line 3682 "gecode/flatzinc/parser.tab.cpp" break; case 130: /* non_array_expr: set_literal */ -#line 1884 "./gecode/flatzinc/parser.yxx" +#line 1888 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = (yyvsp[0].setLit); } -#line 3684 "gecode/flatzinc/parser.tab.cpp" +#line 3688 "gecode/flatzinc/parser.tab.cpp" break; case 131: /* non_array_expr: var_par_id */ -#line 1886 "./gecode/flatzinc/parser.yxx" +#line 1890 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); SymbolEntry e; @@ -3784,11 +3784,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } free((yyvsp[0].sValue)); } -#line 3784 "gecode/flatzinc/parser.tab.cpp" +#line 3788 "gecode/flatzinc/parser.tab.cpp" break; case 132: /* non_array_expr: var_par_id '[' non_array_expr ']' */ -#line 1982 "./gecode/flatzinc/parser.yxx" +#line 1986 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); int i = -1; @@ -3800,35 +3800,35 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); delete (yyvsp[-1].arg); free((yyvsp[-3].sValue)); } -#line 3800 "gecode/flatzinc/parser.tab.cpp" +#line 3804 "gecode/flatzinc/parser.tab.cpp" break; case 133: /* non_array_expr_list: %empty */ -#line 1996 "./gecode/flatzinc/parser.yxx" +#line 2000 "./gecode/flatzinc/parser.yxx" { (yyval.argVec) = new AST::Array(0); } -#line 3806 "gecode/flatzinc/parser.tab.cpp" +#line 3810 "gecode/flatzinc/parser.tab.cpp" break; case 134: /* non_array_expr_list: non_array_expr_list_head list_tail */ -#line 1998 "./gecode/flatzinc/parser.yxx" +#line 2002 "./gecode/flatzinc/parser.yxx" { (yyval.argVec) = (yyvsp[-1].argVec); } -#line 3812 "gecode/flatzinc/parser.tab.cpp" +#line 3816 "gecode/flatzinc/parser.tab.cpp" break; case 135: /* non_array_expr_list_head: non_array_expr */ -#line 2002 "./gecode/flatzinc/parser.yxx" +#line 2006 "./gecode/flatzinc/parser.yxx" { (yyval.argVec) = new AST::Array((yyvsp[0].arg)); } -#line 3818 "gecode/flatzinc/parser.tab.cpp" +#line 3822 "gecode/flatzinc/parser.tab.cpp" break; case 136: /* non_array_expr_list_head: non_array_expr_list_head ',' non_array_expr */ -#line 2004 "./gecode/flatzinc/parser.yxx" +#line 2008 "./gecode/flatzinc/parser.yxx" { (yyval.argVec) = (yyvsp[-2].argVec); (yyval.argVec)->append((yyvsp[0].arg)); } -#line 3824 "gecode/flatzinc/parser.tab.cpp" +#line 3828 "gecode/flatzinc/parser.tab.cpp" break; case 137: /* solve_expr: var_par_id */ -#line 2012 "./gecode/flatzinc/parser.yxx" +#line 2016 "./gecode/flatzinc/parser.yxx" { ParserState *pp = static_cast(parm); SymbolEntry e; @@ -3862,33 +3862,33 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } free((yyvsp[0].sValue)); } -#line 3862 "gecode/flatzinc/parser.tab.cpp" +#line 3866 "gecode/flatzinc/parser.tab.cpp" break; case 138: /* solve_expr: FZ_INT_LIT */ -#line 2046 "./gecode/flatzinc/parser.yxx" +#line 2050 "./gecode/flatzinc/parser.yxx" { ParserState *pp = static_cast(parm); pp->intvars.push_back(varspec("OBJ_CONST_INTRODUCED", new IntVarSpec(0,true,false))); (yyval.iValue) = pp->intvars.size()-1; } -#line 3873 "gecode/flatzinc/parser.tab.cpp" +#line 3877 "gecode/flatzinc/parser.tab.cpp" break; case 139: /* solve_expr: FZ_FLOAT_LIT */ -#line 2053 "./gecode/flatzinc/parser.yxx" +#line 2057 "./gecode/flatzinc/parser.yxx" { ParserState *pp = static_cast(parm); pp->intvars.push_back(varspec("OBJ_CONST_INTRODUCED", new IntVarSpec(0,true,false))); (yyval.iValue) = pp->intvars.size()-1; } -#line 3884 "gecode/flatzinc/parser.tab.cpp" +#line 3888 "gecode/flatzinc/parser.tab.cpp" break; case 140: /* solve_expr: var_par_id '[' FZ_INT_LIT ']' */ -#line 2060 "./gecode/flatzinc/parser.yxx" +#line 2064 "./gecode/flatzinc/parser.yxx" { SymbolEntry e; ParserState *pp = static_cast(parm); @@ -3912,103 +3912,103 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); } free((yyvsp[-3].sValue)); } -#line 3912 "gecode/flatzinc/parser.tab.cpp" +#line 3916 "gecode/flatzinc/parser.tab.cpp" break; case 143: /* annotations: %empty */ -#line 2094 "./gecode/flatzinc/parser.yxx" +#line 2098 "./gecode/flatzinc/parser.yxx" { (yyval.argVec) = NULL; } -#line 3918 "gecode/flatzinc/parser.tab.cpp" +#line 3922 "gecode/flatzinc/parser.tab.cpp" break; case 144: /* annotations: annotations_head */ -#line 2096 "./gecode/flatzinc/parser.yxx" +#line 2100 "./gecode/flatzinc/parser.yxx" { (yyval.argVec) = (yyvsp[0].argVec); } -#line 3924 "gecode/flatzinc/parser.tab.cpp" +#line 3928 "gecode/flatzinc/parser.tab.cpp" break; case 145: /* annotations_head: FZ_COLONCOLON annotation */ -#line 2100 "./gecode/flatzinc/parser.yxx" +#line 2104 "./gecode/flatzinc/parser.yxx" { (yyval.argVec) = new AST::Array((yyvsp[0].arg)); } -#line 3930 "gecode/flatzinc/parser.tab.cpp" +#line 3934 "gecode/flatzinc/parser.tab.cpp" break; case 146: /* annotations_head: annotations_head FZ_COLONCOLON annotation */ -#line 2102 "./gecode/flatzinc/parser.yxx" +#line 2106 "./gecode/flatzinc/parser.yxx" { (yyval.argVec) = (yyvsp[-2].argVec); (yyval.argVec)->append((yyvsp[0].arg)); } -#line 3936 "gecode/flatzinc/parser.tab.cpp" +#line 3940 "gecode/flatzinc/parser.tab.cpp" break; case 147: /* annotation: FZ_ID '(' annotation_list ')' */ -#line 2106 "./gecode/flatzinc/parser.yxx" +#line 2110 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = new AST::Call((yyvsp[-3].sValue), AST::extractSingleton((yyvsp[-1].arg))); free((yyvsp[-3].sValue)); } -#line 3944 "gecode/flatzinc/parser.tab.cpp" +#line 3948 "gecode/flatzinc/parser.tab.cpp" break; case 148: /* annotation: annotation_expr */ -#line 2110 "./gecode/flatzinc/parser.yxx" +#line 2114 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = (yyvsp[0].arg); } -#line 3950 "gecode/flatzinc/parser.tab.cpp" +#line 3954 "gecode/flatzinc/parser.tab.cpp" break; case 149: /* annotation_list: annotation */ -#line 2114 "./gecode/flatzinc/parser.yxx" +#line 2118 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = new AST::Array((yyvsp[0].arg)); } -#line 3956 "gecode/flatzinc/parser.tab.cpp" +#line 3960 "gecode/flatzinc/parser.tab.cpp" break; case 150: /* annotation_list: annotation_list ',' annotation */ -#line 2116 "./gecode/flatzinc/parser.yxx" +#line 2120 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = (yyvsp[-2].arg); (yyval.arg)->append((yyvsp[0].arg)); } -#line 3962 "gecode/flatzinc/parser.tab.cpp" +#line 3966 "gecode/flatzinc/parser.tab.cpp" break; case 151: /* annotation_expr: ann_non_array_expr */ -#line 2120 "./gecode/flatzinc/parser.yxx" +#line 2124 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = (yyvsp[0].arg); } -#line 3968 "gecode/flatzinc/parser.tab.cpp" +#line 3972 "gecode/flatzinc/parser.tab.cpp" break; case 152: /* annotation_expr: '[' ']' */ -#line 2122 "./gecode/flatzinc/parser.yxx" +#line 2126 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = new AST::Array(); } -#line 3974 "gecode/flatzinc/parser.tab.cpp" +#line 3978 "gecode/flatzinc/parser.tab.cpp" break; case 153: /* annotation_expr: '[' annotation_list annotation_list_tail ']' */ -#line 2124 "./gecode/flatzinc/parser.yxx" +#line 2128 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = (yyvsp[-2].arg); } -#line 3980 "gecode/flatzinc/parser.tab.cpp" +#line 3984 "gecode/flatzinc/parser.tab.cpp" break; case 156: /* ann_non_array_expr: FZ_BOOL_LIT */ -#line 2130 "./gecode/flatzinc/parser.yxx" +#line 2134 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = new AST::BoolLit((yyvsp[0].iValue)); } -#line 3986 "gecode/flatzinc/parser.tab.cpp" +#line 3990 "gecode/flatzinc/parser.tab.cpp" break; case 157: /* ann_non_array_expr: FZ_INT_LIT */ -#line 2132 "./gecode/flatzinc/parser.yxx" +#line 2136 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = new AST::IntLit((yyvsp[0].iValue)); } -#line 3992 "gecode/flatzinc/parser.tab.cpp" +#line 3996 "gecode/flatzinc/parser.tab.cpp" break; case 158: /* ann_non_array_expr: FZ_FLOAT_LIT */ -#line 2134 "./gecode/flatzinc/parser.yxx" +#line 2138 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = new AST::FloatLit((yyvsp[0].dValue)); } -#line 3998 "gecode/flatzinc/parser.tab.cpp" +#line 4002 "gecode/flatzinc/parser.tab.cpp" break; case 159: /* ann_non_array_expr: set_literal */ -#line 2136 "./gecode/flatzinc/parser.yxx" +#line 2140 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = (yyvsp[0].setLit); } -#line 4004 "gecode/flatzinc/parser.tab.cpp" +#line 4008 "gecode/flatzinc/parser.tab.cpp" break; case 160: /* ann_non_array_expr: var_par_id */ -#line 2138 "./gecode/flatzinc/parser.yxx" +#line 2142 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); SymbolEntry e; @@ -4114,11 +4114,11 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); (yyval.arg) = getVarRefArg(pp,(yyvsp[0].sValue),true); free((yyvsp[0].sValue)); } -#line 4114 "gecode/flatzinc/parser.tab.cpp" +#line 4118 "gecode/flatzinc/parser.tab.cpp" break; case 161: /* ann_non_array_expr: var_par_id '[' ann_non_array_expr ']' */ -#line 2244 "./gecode/flatzinc/parser.yxx" +#line 2248 "./gecode/flatzinc/parser.yxx" { ParserState* pp = static_cast(parm); int i = -1; @@ -4129,20 +4129,20 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); (yyval.arg) = new AST::IntLit(0); // keep things consistent free((yyvsp[-3].sValue)); } -#line 4129 "gecode/flatzinc/parser.tab.cpp" +#line 4133 "gecode/flatzinc/parser.tab.cpp" break; case 162: /* ann_non_array_expr: FZ_STRING_LIT */ -#line 2255 "./gecode/flatzinc/parser.yxx" +#line 2259 "./gecode/flatzinc/parser.yxx" { (yyval.arg) = new AST::String((yyvsp[0].sValue)); free((yyvsp[0].sValue)); } -#line 4138 "gecode/flatzinc/parser.tab.cpp" +#line 4142 "gecode/flatzinc/parser.tab.cpp" break; -#line 4142 "gecode/flatzinc/parser.tab.cpp" +#line 4146 "gecode/flatzinc/parser.tab.cpp" default: break; } diff --git a/gecode/flatzinc/parser.tab.hpp b/gecode/flatzinc/parser.tab.hpp index 695c7a276d..094a72380a 100644 --- a/gecode/flatzinc/parser.tab.hpp +++ b/gecode/flatzinc/parser.tab.hpp @@ -107,7 +107,7 @@ extern int yydebug; #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED union YYSTYPE { -#line 610 "./gecode/flatzinc/parser.yxx" +#line 614 "./gecode/flatzinc/parser.yxx" int iValue; char* sValue; bool bValue; double dValue; std::vector* setValue; Gecode::FlatZinc::AST::SetLit* setLit; From 1da31c2999b478f80ca75ac5c5892af1d8c1a0fb Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Fri, 10 Jul 2026 10:03:14 +0200 Subject: [PATCH 7/8] Validate current release changelog output Keep module ordering and Float classification deterministic, reject invalid release metadata, and parse exact changelog entries during tidy checks. Record the FlatZinc and Gist compatibility notes for 6.4.0. --- changelog.in | 25 ++++- misc/gencurrentchangelog.py | 28 ++++- misc/tidy.py | 206 ++++++++++++++++++++++++++++++++++++ 3 files changed, 255 insertions(+), 4 deletions(-) diff --git a/changelog.in b/changelog.in index 0e2d79b524..3b5570d293 100755 --- a/changelog.in +++ b/changelog.in @@ -52,8 +52,8 @@ # An entry is described as follows: # [ENTRY] # Module: -# kernel|search|int|set|cpltset|example|minimodel|iter|support| -# driver|test|gist|flatzinc|other +# kernel|search|int|set|float|cpltset|scheduling|graph|minimodel| +# driver|iter|support|example|test|gist|flatzinc|other # What: bug|documentation|performance|new|removed|change # Rank: minor|major # Bug: @@ -69,7 +69,7 @@ [RELEASE] Version: 6.4.0 -Date: 2026-07-07 +Date: 2026-??-?? [DESCRIPTION] This release modernizes the Gecode build infrastructure, adds a first-class CMake package for downstream consumers, refreshes the @@ -83,6 +83,14 @@ Rank: minor Fix MSVC compilation of matrix slice conversion operators by fully qualifying their dependent return types. +[ENTRY] +Module: gist +What: change +Rank: minor +[DESCRIPTION] +Restore the legacy GIST_STATIC_LIBS compatibility macro for static +Windows clients. GECODE_STATIC_LIBS remains the preferred spelling. + [ENTRY] Module: float What: bug @@ -130,6 +138,17 @@ Prefer Qt 6 for Gist and Qt-backed builds while keeping Qt 5.15+ as the supported legacy baseline. Installed CMake packages now request the Qt major/minor version used to build Gecode. +[ENTRY] +Module: flatzinc +What: removed +Rank: major +[DESCRIPTION] +Remove the public GECODE_FLATZINC_VERSION macro. It described a fixed +FlatZinc language revision that no longer matches the capability-based +MiniZinc interface. Code that only needs the Gecode release should use +GECODE_VERSION; FlatZinc integrations should use the installed MiniZinc +solver metadata instead of gating parser use on a language-version macro. + [ENTRY] Module: other What: change diff --git a/misc/gencurrentchangelog.py b/misc/gencurrentchangelog.py index 1a49f750d8..2537024980 100644 --- a/misc/gencurrentchangelog.py +++ b/misc/gencurrentchangelog.py @@ -21,6 +21,7 @@ def main() -> int: "search": "Search engines", "int": "Finite domain integers", "set": "Finite integer sets", + "float": "Floats", "cpltset": "Finite integer sets with complete representation", "minimodel": "Minimal modeling support", "graph": "Graph constraints", @@ -51,6 +52,7 @@ def main() -> int: "search", "int", "set", + "float", "cpltset", "scheduling", "graph", @@ -72,6 +74,7 @@ def main() -> int: lines = sys.stdin.readlines() idx = 0 current = None + entry_number = 0 while True: if current is None: @@ -131,6 +134,7 @@ def main() -> int: continue if re.match(r"^\[ENTRY\]", l): + entry_number += 1 desc = "" bug = "" rank = "" @@ -175,8 +179,30 @@ def main() -> int: if desc.endswith("\n"): desc = desc[:-1] + if mod not in modclear or mod not in modorder: + print( + f"gencurrentchangelog: entry {entry_number}: " + f"unknown module {mod!r}", + file=sys.stderr, + ) + return 1 + if what not in whatclear or what not in whatorder: + print( + f"gencurrentchangelog: entry {entry_number}: " + f"unknown change type {what!r}", + file=sys.stderr, + ) + return 1 + if rank not in rankclear: + print( + f"gencurrentchangelog: entry {entry_number}: " + f"unknown rank {rank!r}", + file=sys.stderr, + ) + return 1 + hastext[mod] = 1 - rb = rankclear.get(rank, "") + rb = rankclear[rank] if bug != "": rb = rb + f", bugzilla entry {bug}" if thanks != "": diff --git a/misc/tidy.py b/misc/tidy.py index aec0ef0adc..592c699390 100644 --- a/misc/tidy.py +++ b/misc/tidy.py @@ -12,7 +12,9 @@ import re import subprocess import sys +from collections import Counter from dataclasses import dataclass +from html.parser import HTMLParser from pathlib import Path @@ -20,6 +22,7 @@ CHANGELOG_IN = ROOT / "changelog.in" CHANGELOG_HH = ROOT / "changelog.hh" GEN_CHANGELOG = ROOT / "misc" / "genchangelog.py" +GEN_CURRENT_CHANGELOG = ROOT / "misc" / "gencurrentchangelog.py" MODULES = { "kernel", @@ -87,6 +90,10 @@ def generate_changelog() -> str: return result.stdout +def generate_current_changelog(source: str) -> subprocess.CompletedProcess[str]: + return run(["uv", "run", str(GEN_CURRENT_CHANGELOG)], stdin=source) + + def is_tracked(path: Path) -> bool: rel = str(path.relative_to(ROOT)) return run(["git", "ls-files", "--error-unmatch", rel]).returncode == 0 @@ -263,9 +270,208 @@ def validate_changelog_entries(generated: str) -> list[str]: return errors +def current_changelog_text(raw: str) -> str: + description = "" + for line in raw.splitlines(keepends=True): + description += line if not description else " " + line + if description.endswith("\n"): + description = description[:-1] + return description.replace("%Gecode", "Gecode") + + +def normalize_rendered_text(text: str) -> str: + return " ".join(text.split()) + + +def current_description_from_body(body: str) -> str | None: + if not body.endswith(")"): + return None + depth = 0 + for position in range(len(body) - 1, -1, -1): + if body[position] == ")": + depth += 1 + elif body[position] == "(": + depth -= 1 + if depth == 0: + metadata = body[position + 1 : -1] + if re.match(r"^(?:minor|major)(?:,|$)", metadata): + return body[:position].rstrip() + return None + return None + + +class CurrentChangelogEntryParser(HTMLParser): + def __init__(self) -> None: + super().__init__() + self._items: list[tuple[list[str], bool]] = [] + self.entries: list[str] = [] + self.errors: list[str] = [] + + def handle_starttag( + self, tag: str, attrs: list[tuple[str, str | None]] + ) -> None: + del attrs + if tag == "li": + self._items.append(([], False)) + elif tag == "ul" and self._items: + text, _ = self._items[-1] + self._items[-1] = (text, True) + + def handle_data(self, data: str) -> None: + if self._items: + self._items[-1][0].append(data) + + def handle_endtag(self, tag: str) -> None: + if tag != "li" or not self._items: + return + text, has_nested_list = self._items.pop() + if has_nested_list: + return + body = normalize_rendered_text("".join(text)) + description = current_description_from_body(body) + if description is None: + self.errors.append(f"entry has no rank metadata: {body!r}") + return + self.entries.append(description) + + +def rendered_current_descriptions(html: str) -> tuple[Counter[str], list[str]]: + parser = CurrentChangelogEntryParser() + parser.feed(html) + parser.close() + return Counter(parser.entries), parser.errors + + +def compare_current_descriptions( + html: str, expected: list[str], *, context: str +) -> list[str]: + actual, errors = rendered_current_descriptions(html) + expected_counts = Counter(normalize_rendered_text(text) for text in expected) + errors = [f"{context}: {error}" for error in errors] + + for description in sorted(expected_counts.keys() | actual.keys()): + expected_count = expected_counts[description] + actual_count = actual[description] + if actual_count != expected_count: + errors.append( + f"{context}: entry {description!r} appears {actual_count} times, " + f"expected {expected_count}" + ) + return errors + + +def validate_current_changelog() -> list[str]: + source = CHANGELOG_IN.read_text() + result = generate_current_changelog(source) + if result.returncode != 0: + return [ + "current-release changelog generator failed: " + + (result.stderr.strip() or result.stdout.strip()) + ] + + releases, entries = parse_changelog() + if not releases: + return ["changelog has no release for current-release generation"] + current_entries = [entry for entry in entries if entry.release == releases[0]] + expected_descriptions = [ + current_changelog_text(entry.description) for entry in current_entries + ] + errors = compare_current_descriptions( + result.stdout, expected_descriptions, context="current-release HTML" + ) + + regression_source = """[RELEASE] +Version: 1.0.0 +Date: 2000-01-01 +[DESCRIPTION] +Test release. + +[ENTRY] +Module: other +What: change +Rank: minor +[DESCRIPTION] +Short. + +[ENTRY] +Module: other +What: change +Rank: minor +[DESCRIPTION] +Short. longer. + +[ENTRY] +Module: other +What: change +Rank: minor +[DESCRIPTION] +Short. + +[RELEASE] +Version: 0.9.0 +Date: 1999-01-01 +[DESCRIPTION] +Previous release. +""" + regression_result = generate_current_changelog(regression_source) + if regression_result.returncode != 0: + errors.append( + "current-release exact-entry regression failed to generate: " + + (regression_result.stderr.strip() or regression_result.stdout.strip()) + ) + else: + errors.extend( + compare_current_descriptions( + regression_result.stdout, + ["Short.", "Short. longer.", "Short."], + context="current-release exact-entry regression", + ) + ) + + invalid_fields = { + "Module": ("unknown module", "invalid-module"), + "What": ("unknown change type", "invalid-what"), + "Rank": ("unknown rank", "invalid-rank"), + } + for field, (expected_error, value) in invalid_fields.items(): + metadata = { + "Module": "other", + "What": "change", + "Rank": "minor", + } + metadata[field] = value + invalid_source = f"""[RELEASE] +Version: 1.0.0 +Date: 2000-01-01 +[DESCRIPTION] +Test release. + +[ENTRY] +Module: {metadata['Module']} +What: {metadata['What']} +Rank: {metadata['Rank']} +[DESCRIPTION] +Test entry. + +[RELEASE] +Version: 0.9.0 +Date: 1999-01-01 +[DESCRIPTION] +Previous release. +""" + invalid_result = generate_current_changelog(invalid_source) + if invalid_result.returncode == 0 or expected_error not in invalid_result.stderr: + errors.append( + f"current-release generator did not reject invalid {field.lower()}" + ) + + return errors + + def check_changelog(*, fix: bool) -> int: generated = generate_changelog() errors = validate_changelog_entries(generated) + errors.extend(validate_current_changelog()) if is_tracked(CHANGELOG_HH): if CHANGELOG_HH.exists() and CHANGELOG_HH.read_text() == generated: From 6931d710eb548e24404d598680621e609d3d5a0a Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Fri, 10 Jul 2026 10:03:18 +0200 Subject: [PATCH 8/8] Tighten repository hygiene Ignore the complete set of in-tree example executables without broad patterns, and restore the vendored Boost backup file as valid UTF-8 text. --- .github/ci/cmake-custom-vis-smoke.sh | 6 +- .github/ci/generated-source-freshness.sh | 122 +++++++++++++++++- .github/workflows/build.yml | 6 +- .gitignore | 63 ++++++++- Makefile.in | 4 + README.md | 5 + .../detail/msvc_rounding_control.hpp.orig | 2 +- 7 files changed, 195 insertions(+), 13 deletions(-) diff --git a/.github/ci/cmake-custom-vis-smoke.sh b/.github/ci/cmake-custom-vis-smoke.sh index cfca5d592e..0dec437ec5 100755 --- a/.github/ci/cmake-custom-vis-smoke.sh +++ b/.github/ci/cmake-custom-vis-smoke.sh @@ -72,7 +72,11 @@ test "$float_line" -lt "$custom_one_line" test "$custom_one_line" -lt "$custom_two_line" mtime() { - stat -f '%m' "$1" 2>/dev/null || stat -c '%Y' "$1" + if stat -c '%Y' "$1" >/dev/null 2>&1; then + stat -c '%Y' "$1" + else + stat -f '%m' "$1" + fi } type_mtime="$(mtime "$type_header")" imp_mtime="$(mtime "$imp_header")" diff --git a/.github/ci/generated-source-freshness.sh b/.github/ci/generated-source-freshness.sh index 17e7ebb5f3..92cce3ae6f 100755 --- a/.github/ci/generated-source-freshness.sh +++ b/.github/ci/generated-source-freshness.sh @@ -1,6 +1,20 @@ #!/usr/bin/env bash set -euo pipefail +if [ "$#" -gt 1 ]; then + echo "usage: $0 [--check|--update]" >&2 + exit 2 +fi +mode=check +case "${1:-}" in + ""|--check) ;; + --update) mode=update ;; + *) + echo "usage: $0 [--check|--update]" >&2 + exit 2 + ;; +esac + repo_root="${GECODE_SOURCE_ROOT:-$(git rev-parse --show-toplevel)}" temp_root="${RUNNER_TEMP:-${TMPDIR:-/tmp}}" @@ -14,9 +28,13 @@ if [ ! -d "$repo_root/.git" ] && [ ! -f "$repo_root/.git" ]; then fi cleanup_root= +autoconf_build_root= workspace= pending_signal= cleanup() { + if [ -n "$autoconf_build_root" ]; then + rm -rf -- "$autoconf_build_root" + fi if [ -n "$cleanup_root" ]; then rm -rf -- "$cleanup_root" fi @@ -53,6 +71,94 @@ workspace=$(mktemp -d "$cleanup_root/work.XXXXXX") source_copy="$workspace/source" mkdir "$source_copy" +autoconf_version=2.72 +autoconf_expected="autoconf (GNU Autoconf) $autoconf_version" +autoconf_sha256=afb181a76e1ee72832f6581c0eddf8df032b83e2e0239ef79ebedc4467d92d6e + +prepend_brew_bin() { + formula=$1 + if ! command -v brew >/dev/null 2>&1; then + return + fi + prefix=$(brew --prefix "$formula" 2>/dev/null || true) + if [ -n "$prefix" ] && [ -d "$prefix/bin" ]; then + PATH="$prefix/bin:$PATH" + fi +} + +# Homebrew keeps bison, flex, and m4 keg-only on macOS. Make the same helper +# work from a developer shell without requiring them to edit PATH manually. +prepend_brew_bin m4 +prepend_brew_bin bison +prepend_brew_bin flex +export PATH + +autoconf_is_expected() { + command -v autoconf >/dev/null 2>&1 && + [ "$(autoconf --version | sed -n '1p')" = "$autoconf_expected" ] +} + +ensure_autoconf() { + if autoconf_is_expected; then + return + fi + + if [ -n "${GECODE_GENERATED_TOOLS_DIR:-}" ]; then + tools_root=$GECODE_GENERATED_TOOLS_DIR + elif [ -n "${RUNNER_TEMP:-}" ]; then + tools_root="$RUNNER_TEMP/gecode-generated-tools" + else + tools_root="${XDG_CACHE_HOME:-$HOME/.cache}/gecode/generated-tools" + fi + install_root="$tools_root/autoconf-$autoconf_version" + + if [ -x "$install_root/bin/autoconf" ]; then + PATH="$install_root/bin:$PATH" + export PATH + if autoconf_is_expected; then + return + fi + fi + + for tool in curl make tar; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo "Cannot bootstrap Autoconf $autoconf_version: missing $tool." >&2 + exit 1 + fi + done + if ! command -v shasum >/dev/null 2>&1 && + ! command -v sha256sum >/dev/null 2>&1; then + echo "Cannot bootstrap Autoconf $autoconf_version: no SHA-256 checker is available." >&2 + exit 1 + fi + if ! command -v m4 >/dev/null 2>&1; then + echo "Cannot bootstrap Autoconf $autoconf_version: GNU m4 is unavailable." >&2 + exit 1 + fi + + mkdir -p "$tools_root" + autoconf_build_root=$(mktemp -d "$temp_root/gecode-autoconf-build.XXXXXX") + archive="$autoconf_build_root/autoconf-$autoconf_version.tar.gz" + curl --fail --silent --show-error --location --retry 3 \ + --output "$archive" \ + "https://ftp.gnu.org/gnu/autoconf/autoconf-$autoconf_version.tar.gz" + if command -v shasum >/dev/null 2>&1; then + printf '%s %s\n' "$autoconf_sha256" "$archive" | shasum -a 256 -c - + else + printf '%s %s\n' "$autoconf_sha256" "$archive" | sha256sum -c - + fi + tar -xzf "$archive" -C "$autoconf_build_root" + rm -rf -- "$install_root" + ( + cd "$autoconf_build_root/autoconf-$autoconf_version" + ./configure --prefix="$install_root" >/dev/null + make -j2 >/dev/null + make install >/dev/null + ) + PATH="$install_root/bin:$PATH" + export PATH +} + require_version() { tool=$1 expected=$2 @@ -65,12 +171,13 @@ require_version() { echo "Unsupported $tool version." >&2 echo " required: $expected" >&2 echo " found: $actual" >&2 - echo "Use the pinned generator version or regenerate and review all tracked outputs intentionally." >&2 + echo "Install the required generator, then run 'make regenerate'." >&2 exit 1 fi } -require_version autoconf "autoconf (GNU Autoconf) 2.72" +ensure_autoconf +require_version autoconf "$autoconf_expected" require_version bison "bison (GNU Bison) 3.8.2" require_version flex "flex 2.6.4" if ! command -v uv >/dev/null 2>&1; then @@ -147,6 +254,11 @@ for file in "${generated_files[@]}"; do if cmp -s "$repo_root/$file" "$source_copy/$file"; then continue fi + if [ "$mode" = update ]; then + cp "$source_copy/$file" "$repo_root/$file" + echo "Updated generated source: $file" + continue + fi stale=1 echo "Generated source is stale: $file" >&2 diff -u \ @@ -155,8 +267,14 @@ for file in "${generated_files[@]}"; do "$repo_root/$file" "$source_copy/$file" >&2 || true done +if [ "$mode" = update ]; then + echo "Generated source regeneration completed" + exit 0 +fi + if [ "$stale" -ne 0 ]; then echo "Regenerate with Autoconf 2.72, GNU Bison 3.8.2, GNU Flex 2.6.4, and uv, then commit the reviewed outputs." >&2 + echo "Run 'make regenerate' to use the canonical generator workflow." >&2 exit 1 fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5c5853a26..58922c9c9b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,10 +55,8 @@ jobs: if: runner.os == 'macOS' shell: bash run: | - # The freshness script enforces exact versions and fails on formula drift. - brew install autoconf bison flex - echo "$(brew --prefix bison)/bin" >> "$GITHUB_PATH" - echo "$(brew --prefix flex)/bin" >> "$GITHUB_PATH" + # The freshness helper bootstraps the pinned Autoconf version itself. + brew install m4 bison flex - name: Check generated source freshness if: runner.os == 'macOS' diff --git a/.gitignore b/.gitignore index baa4a7fcf5..9e4fc48bb1 100644 --- a/.gitignore +++ b/.gitignore @@ -48,11 +48,64 @@ gmon.out doc GecodeReference.chm ChangeLog -examples/* -!examples/CMakeLists.txt -!examples/*.cpp -!examples/*.hpp -!examples/*.hh +# In-source example executables; source, data, and documentation stay visible. +/examples/all-interval +/examples/alpha +/examples/archimedean-spiral +/examples/bacp +/examples/bibd +/examples/bin-packing +/examples/black-hole +/examples/car-sequencing +/examples/cartesian-heart +/examples/colored-matrix +/examples/crew +/examples/crossword +/examples/crowded-chess +/examples/descartes-folium +/examples/dominating-queens +/examples/domino +/examples/donald +/examples/efpa +/examples/eq20 +/examples/golden-spiral +/examples/golf +/examples/golomb-ruler +/examples/graph-color +/examples/grocery +/examples/hamming +/examples/ind-set +/examples/job-shop +/examples/kakuro +/examples/knights +/examples/langford-number +/examples/magic-sequence +/examples/magic-square-partial +/examples/magic-square +/examples/minesweeper +/examples/money +/examples/multi-bin-packing +/examples/nonogram +/examples/open-shop +/examples/ortho-latin +/examples/partition +/examples/pentominoes +/examples/perfect-square +/examples/photo +/examples/qcp +/examples/queen-armies +/examples/queens +/examples/radiotherapy +/examples/sat +/examples/schurs-lemma +/examples/sports-league +/examples/steel-mill +/examples/steiner +/examples/sudoku-advanced +/examples/sudoku +/examples/tsp +/examples/warehouses +/examples/word-square test/test # Ignore build tree outputs (version metadata lives in top-level diff --git a/Makefile.in b/Makefile.in index 76b00aba16..1a0f806ccc 100755 --- a/Makefile.in +++ b/Makefile.in @@ -1349,6 +1349,10 @@ check: test $(RUNENVIRONMENT) $(TESTEXE) -iter 2 -threads 0 -fixprob 1 \ $(CHECKTESTS:%=-test %) +.PHONY: regenerate +regenerate: + bash "$(top_srcdir)/.github/ci/generated-source-freshness.sh" --update + ifeq "@top_srcdir@" "." mkcompiledirs: else diff --git a/README.md b/README.md index c26e8aeda7..bf55a9e2c1 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,11 @@ Build-time script execution uses `uv run --script ...` and requires Python for generated-script paths such as `-DGECODE_REGENERATE_VARIMP=ON` and the `doc` target. Documentation generation requires Doxygen 1.17.0 or newer. +When changing generated-source inputs, run `make regenerate` after configuring +the project. This uses the same version-checked workflow as CI and caches the +Autoconf 2.72 bootstrap locally; Bison 3.8.2, Flex 2.6.4, and `uv` must be +available. Ordinary C++ changes do not require regeneration. + `GECODE_ENABLE_QT` and `GECODE_ENABLE_GIST` accept `AUTO`, `ON`, and `OFF`. Use `ON` to require the dependency and fail configuration if it is missing or too old, `OFF` to disable it, and `AUTO` to use it when the local toolchain diff --git a/gecode/third-party/boost/numeric/interval/detail/msvc_rounding_control.hpp.orig b/gecode/third-party/boost/numeric/interval/detail/msvc_rounding_control.hpp.orig index 45597f6d5f..c0dc8fce04 100644 --- a/gecode/third-party/boost/numeric/interval/detail/msvc_rounding_control.hpp.orig +++ b/gecode/third-party/boost/numeric/interval/detail/msvc_rounding_control.hpp.orig @@ -1,7 +1,7 @@ /* Boost interval/detail/msvc_rounding_control.hpp file * * Copyright 2000 Maarten Keijzer - * Copyright 2002 Hervé Brönnimann, Guillaume Melquiond, Sylvain Pion + * Copyright 2002 HervĂ© Brönnimann, Guillaume Melquiond, Sylvain Pion * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or