Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ The `build.toml` file defines the structure and dependencies of your XMS library
| `pybind_advertises_module` | boolean | `false` | Advertise the pybind module's import library (`_<name>`) to C++ consumers instead of the static library, and install the module to `bin/` + `lib/`. Windows-only in effect; opt-in because module consumers see only exported symbols. Also what renames the Windows Debug module to `_<name>_d`, matching what `cpp_info` advertises. See `docs/USAGE.md` §7.5. |
| `[matrix].compiler_runtime` | array[string] | `["dynamic", "static"]` | Which MSVC runtimes the fan-out builds. `["dynamic"]` drops the static-CRT configurations (and their `wchar_t` / `testing` copies) for a library nothing consumes a `/MT` build of. Inert on Linux and macOS. See `docs/USAGE.md` §5.4.1. |
| `[matrix].wheel_only` | boolean | `false` | Build only what a wheel release needs — Release with tests, Debug with tests, and the pybind build. Three configurations for a single-ABI library (down from 5 on Linux/macOS, 13 on Windows), matching in everything but `build_type` and `pybind`. The pybind leg fans out, so each extra `python_versions` or `pybind_build_types` entry adds one configuration on top of the three. Drops the library-only and `wchar_t=typedef` configurations and narrows `compiler_runtime` to `["dynamic"]` unless set explicitly. See `docs/USAGE.md` §5.4.1. |
| `[ci].windows_vs2019` | boolean | `false` | Also build the `windows_vs2019` (msvc 192) matrix in CI and publish it to the `aquaveo-vs2019` remote. **GitLab only** — a GitHub project setting it is warned and gets no jobs. Emitted beside the msvc 194 Windows jobs, so it requires `[ci].windows` and is rejected with it off. No wheels: an msvc 192 wheel and an msvc 194 wheel are the same devpi filename. See `docs/USAGE.md` §10.2. |
| `[matrix].pybind_build_types` | array[string] | `["Release"]` | Which build types get a pybind configuration. Add `"Debug"` when consumers link a Debug module; `XMS_COVERAGE=1` no longer adds `Debug` on top, because coverage takes its Python half from the Release pybind build. On Windows the Debug leg publishes no wheel and runs no Python tests. See `docs/USAGE.md` §5.4.1 and §7.5. |

### Build Matrix Filter (`[filter]`)
Expand Down Expand Up @@ -380,9 +381,11 @@ On Windows, use `nextms-dev-x86` for x86_64 Linux builds. The commands are ident

## VS2019 (msvc 192) Packages

GitHub retired the `windows-2019` runner image, so the msvc 192 binaries the Aquaveo desktop products (GMS/SMS/WMS) consume are built **manually, on a developer workstation with Visual Studio 2019 installed**, and published to a separate Conan remote, `aquaveo-vs2019`.
The msvc 192 binaries the Aquaveo desktop products (GMS/SMS/WMS) consume are published to a separate Conan remote, `aquaveo-vs2019`, so they never mix with the CI-published ones. `xmsconan vs2019` drives that build **manually, on a developer workstation with Visual Studio 2019 installed**.

**None of this runs in CI, by design.** CI is unchanged: it still builds gcc 13 / apple-clang 17 / msvc 194 and publishes to the `aquaveo` remote (the `aquaveo-stable` Artifactory repo). There is no Windows-2019 CI job to restore — the runner image is gone.
**GitLab CI can build this matrix too.** The `GLR-UV` runner carries VS2019 alongside VS2022, so a repository can set `[ci].windows_vs2019 = true` and get a `Conan Build - Windows VS2019` job on every pipeline, publishing to the same `aquaveo-vs2019` remote — see `docs/USAGE.md` §10.2. GitHub cannot: it retired the `windows-2019` image and has no replacement, so a GitHub project setting the flag is warned and gets no jobs.

The manual track below stays, and is still the only route for **wheels** (CI deliberately publishes none for msvc 192 — a wheel's tags do not record which MSVC built it, so it would collide with the msvc 194 wheel on devpi) and for **libraries whose repository has not opted in**.

```bash
# One time: add + log in to the aquaveo-vs2019 remote, then check the machine
Expand Down
54 changes: 39 additions & 15 deletions docs/USAGE.md

Large diffs are not rendered by default.

130 changes: 128 additions & 2 deletions tests/test_build_file_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,15 @@ def test_copy_xms_conan2_file_copies(tmp_path):

# --- Template generation tests ---

REAL_TEMPLATE_DIR = Path(__file__).resolve().parent.parent / "xmsconan" / "generator_tools" / "templates"
#: This checkout's root, derived from the test file's own location.
#:
#: Deliberately not from an imported module's ``__file__``: under an installed
#: (non-editable) layout that resolves into site-packages, so anything using it
#: to pin a subprocess to "this tree" would silently pin it to another one --
#: which is the exact failure the pin exists to prevent.
REPO_ROOT = Path(__file__).resolve().parent.parent

REAL_TEMPLATE_DIR = REPO_ROOT / "xmsconan" / "generator_tools" / "templates"


def _copy_template(name, dest_dir):
Expand Down Expand Up @@ -706,6 +714,121 @@ def test_generated_build_py_passes_the_matrix_to_the_packager(tmp_path):
assert "matrix=conanfile.CONAN_MATRIX," in content


def _render_build_py_text(tmp_path):
"""Render build.py from this repository's template and return its text."""
toml_file = tmp_path / "build.toml"
toml_file.write_text(
'library_name = "xmscore"\ndescription = "desc"\n', encoding="utf-8"
)
tpl_dir = tmp_path / "tpl"
tpl_dir.mkdir()
_copy_template("build.py.jinja", tpl_dir)
output_dir = tmp_path / "output"
render_template_with_toml(
toml_file_path=str(toml_file),
version="1.0.0",
template_dir=str(tpl_dir),
output_dir=str(output_dir),
)
return (output_dir / "build.py").read_text(encoding="utf-8")


def test_generated_build_py_carries_the_vs2019_identity_as_literals(tmp_path):
"""The msvc 192 platform, remote and version are rendered in, not imported.

build.py already calls into the installed xmsconan for the packager, but a
*new name* in xmsconan.constants would make a freshly generated build.py
die on an ImportError at line 8 against an older installed client -- before
argparse, with nothing said about versions. The values are rendered from
the same constants, so they cannot disagree with the tools that read them.
"""
content = _render_build_py_text(tmp_path)

assert 'VS2019_PLATFORM = "windows_vs2019"' in content
assert 'VS2019_REMOTE = "aquaveo-vs2019"' in content
assert 'VS2019_MSVC_VERSION = "192"' in content
assert "from xmsconan.constants import" not in content
compile(content, "build.py", "exec")


def test_generated_build_py_ties_boost_defaults_and_upload_remote_to_platform(tmp_path):
"""--platform alone decides the matrix, the boost defaults and the remote.

All three have to move together. The boost option defaults name conan-center
boost 1.86 options that the legacy boost/1.74.0.3 recipe does not declare,
and Conan fails a build outright when a profile sets an option no recipe in
the graph defines; the msvc 192 binaries belong on their own remote, and
`conan upload` matches by reference, so a query is what keeps a machine's
msvc 194 binaries from going with them. Deriving all three from one flag is
what makes it impossible to set the matrix and the destination differently.
"""
content = _render_build_py_text(tmp_path)

assert "apply_boost_defaults = args.platform != VS2019_PLATFORM" in content
assert "apply_boost_defaults=apply_boost_defaults," in content
assert "builder.generate_configurations(system_platform=args.platform)" in content
assert "if args.platform == VS2019_PLATFORM:" in content
assert '"remote": VS2019_REMOTE,' in content
assert '"package_query": f"compiler.version={VS2019_MSVC_VERSION}",' in content


def _preview_column(stdout, header):
"""Return one column of ``build.py --preview``'s table, one entry per configuration.

The table is a tabulate grid, so the rows worth reading are the ones whose
first cell is the configuration number; the separator rows and the banner
above the table are not.
"""
rows = [
[cell.strip() for cell in line.strip().strip("|").split("|")]
for line in stdout.splitlines()
if line.strip().startswith("|")
]
if not rows:
return []
index = rows[0].index(header)
return [row[index] for row in rows[1:] if row[0].isdigit()]


def test_generated_build_py_builds_the_msvc_192_matrix_when_asked(tmp_path):
"""`build.py --preview --platform windows_vs2019` really produces msvc 192.

The neighbouring tests assert the flag is rendered and wired; this one runs
it, because "wired to generate_configurations" and "generates the legacy
matrix" are different claims -- and the platform key is a string the
packager could stop recognizing without any of the text changing.
"""
toml_file = tmp_path / "build.toml"
toml_file.write_text('library_name = "xmscore"\ndescription = "Core"\n', encoding="utf-8")
output_dir = tmp_path / "output"
render_template_with_toml(
toml_file_path=str(toml_file),
version="1.0.0",
template_dir=str(REAL_TEMPLATE_DIR),
output_dir=str(output_dir),
)
copy_xms_conan2_file(str(output_dir))

# PYTHONPATH pins the subprocess to *this* checkout. build.py runs from the
# generated directory, so it would otherwise import whatever xmsconan is
# installed on the machine and the test would grade someone else's tree.
result = subprocess.run(
[sys.executable, "build.py", "--preview", "--platform", "windows_vs2019"],
cwd=str(output_dir), capture_output=True, text=True,
env={**os.environ, "PYTHONPATH": str(REPO_ROOT)},
)

assert result.returncode == 0, result.stdout + result.stderr
assert "boost defaults not applied" in result.stdout
# Read the compiler.version column rather than searching stdout for "192".
# A substring check passes on any table that mentions the number anywhere —
# a package version, a path — and `"194" not in stdout` would pass on an
# empty matrix, which is the failure most worth catching here.
versions = _preview_column(result.stdout, "compiler.version")
assert versions, f"no configurations in the preview table:\n{result.stdout}"
assert set(versions) == {"192"}, versions


def test_generated_build_py_can_skip_the_dependency_libs_pass(tmp_path):
"""build.py exposes --skip-dependency-libs, which the CI passes when repair is off.

Expand Down Expand Up @@ -984,7 +1107,10 @@ def test_generated_build_py_exits_nonzero_on_upload_failure(build_toml, tmp_path
content = (output_dir / "build.py").read_text(encoding="utf-8")
# Matched loosely: the guarantee is that upload()'s return code is compared
# against 0 and drives exit(1), not the exact spacing of the generated line.
assert re.search(r"if\s+builder\.upload\(version=args\.version\)\s*!=\s*0\s*:", content)
assert re.search(
r"if\s+builder\.upload\(version=args\.version,\s*\*\*upload_kwargs\)\s*!=\s*0\s*:",
content,
)
assert "exit(1)" in content
compile(content, "build.py", "exec")

Expand Down
Loading
Loading