Spec: M1 — the zixl command builds installer packages #1

Closed
opened 2026-10-14 01:24:39 -05:00 by jeff · 0 comments
Owner

Problem Statement

A Zig project that ships a Windows program wants to hand its users an .msi installer package, but building one today means either the WiX Toolset — a .NET program at home on Windows — or GNOME msitools' wixl, the reference implementation, which stands on glib, libgsf, libgcab, libxml2 and libmsi and does not build easily on Windows. Neither fits a project built with Zig and Nix on Linux CI runners, and neither produces the same bytes twice, so a Nix-built installer package is never reproducible. wixl and libmsi are also LGPL, so nothing derived from their code can become an MIT-licensed dependency of an ordinary build.zig.

Solution

zixl, a command and a Zig library that turn WiX v3 sources into installer packages on Linux or Windows, written in Zig 0.17 with no C libraries (ADR-0002). It is a clean-room reimplementation of what the reference implementation does, written from the public specifications and licensed MIT (ADR-0001). Its command line takes the same options as the reference implementation wherever zixl implements the feature behind them, so a script switches over by changing one word. Its output is reproducible: the same WiX sources and source paths always give the same bytes (ADR-0003).

This spec covers milestone 1: the zixl command builds a real per-machine installer package for a command-line program, for x86, x64 or arm64, which installs under Windows Installer.

User Stories

Building an installer package

  1. As a release engineer, I want to run zixl -o app.msi app.wxs, so that I get an installer package from a WiX source without installing the WiX Toolset or msitools.
  2. As a release engineer, I want to pass several WiX sources on one command line, so that a product split across fragment files is built into one installer package.
  3. As a release engineer on Linux, I want zixl to build installer packages, so that my CI runners never need Windows.
  4. As a developer on Windows, I want the same zixl to run natively, so that I can build the installer package locally the way CI does.
  5. As a release engineer, I want an installer package built from the same inputs to be byte-for-byte identical on every run, so that my Nix build of it is reproducible.
  6. As a release engineer, I want an installer package built by the Windows build of zixl to be byte-identical to the one built on Linux, so that it does not matter which machine made a release.
  7. As a release engineer, I want timestamps in the installer package to come from SOURCE_DATE_EPOCH when it is set, so that reproducible builds do not record the build time.
  8. As a release engineer, I want the output written where -o says, and named the way the reference implementation names it when -o is left out, so that zixl behaves the way the reference implementation does.
  9. As a release engineer migrating from the reference implementation, I want -o, -D, -a/--arch, -I/--includedir, -E, -v and --version to mean what they mean to wixl, so that my scripts work after changing one word.
  10. As a release engineer migrating from the reference implementation, I want an option zixl does not implement, such as --ext, --extdir or --wxidir, to stop with an error saying it is not supported, so that I am never handed an installer package that silently lacks something.

Architectures

  1. As a release engineer, I want -a x64 to produce a 64-bit installer package, so that my program installs under Program Files rather than Program Files (x86).
  2. As a release engineer, I want -a x86 and -a arm64 as well, so that I can ship every Windows architecture from one WiX source.
  3. As a release engineer, I want x64 to be the default architecture, so that the common case needs no option.
  4. As a WiX author, I want components to be marked 64-bit automatically in an x64 or arm64 installer package, so that registry values and files land in the 64-bit views.
  5. As a WiX author, I want $(sys.BUILDARCH) to name the architecture being built, so that one WiX source can choose directories and conditions per architecture.

Preprocessing

  1. As a WiX author, I want <?define?> and <?undef?>, so that I can name values once and use them throughout.
  2. As a WiX author, I want -D NAME=VALUE on the command line to define a variable, so that CI can pass the version number in.
  3. As a WiX author, I want $(var.NAME), $(env.NAME) and $(sys.CURRENTDIR), $(sys.SOURCEFILEDIR) and $(sys.SOURCEFILEPATH), so that sources can refer to definitions, the environment and their own location.
  4. As a WiX author, I want <?if?>, <?ifdef?>, <?ifndef?>, <?else?>, <?elseif?> and <?endif?>, so that one source can vary by architecture or configuration.
  5. As a WiX author, I want <?include?> to pull in .wxi files, searched for relative to the including file and then in each -I directory, so that shared fragments live in one place.
  6. As a WiX author, I want <?foreach?>, so that repetitive elements can be generated from a list.
  7. As a WiX author, I want <?warning?> and <?error?>, so that a source can refuse a configuration it does not support.
  8. As a WiX author, I want zixl -E to print the preprocessed source and stop, so that I can see what my conditionals and includes produced.
  9. As a WiX author, I want an undefined variable to be an error naming it and where it was used, so that a typo does not silently become an empty string.

WiX elements

  1. As a WiX author, I want Product and Package to set the product name, version, manufacturer, language, codes and package summary information, so that Windows shows my installer correctly.
  2. As a WiX author, I want Directory and DirectoryRef, including the standard folders such as ProgramFiles64Folder, so that I can lay out where things are installed.
  3. As a WiX author, I want Component, ComponentRef, ComponentGroup and ComponentGroupRef, so that I can group what is installed together and reference groups from features.
  4. As a WiX author, I want File, with its source path, so that my program's files are put into the installer package.
  5. As a WiX author, I want Feature and FeatureRef, so that components are attached to what the user installs.
  6. As a WiX author, I want Fragment, so that parts of the installer can live in separate WiX sources and be referenced.
  7. As a WiX author, I want Property, so that I can set Windows Installer properties.
  8. As a WiX author, I want Icon, so that my product shows an icon in Add/Remove Programs and on shortcuts.
  9. As a WiX author, I want Shortcut, so that my program appears in the Start menu.
  10. As a WiX author, I want RegistryKey and RegistryValue, so that my program can record its install location or register itself.
  11. As a WiX author, I want Environment, so that my command-line program's directory can be added to PATH.
  12. As a WiX author, I want CreateFolder and RemoveFolder, so that empty directories are created and cleaned up.
  13. As a WiX author, I want MajorUpgrade, so that installing a new version replaces the old one.
  14. As a WiX author, I want Condition as a launch condition, so that the installer refuses to run where my program cannot.
  15. As a WiX author, I want Media, with Cabinet, EmbedCab and DiskId, so that I control how files are packed.
  16. As a WiX author, I want an element zixl does not support to be an error naming it and where it is, so that an installer package is never quietly missing part of what I wrote.

Identity and GUIDs

  1. As a WiX author, I want Id="*" on Product and Package and Guid="*" on Component to have zixl generate the GUID, so that I do not have to make them up.
  2. As a release engineer, I want a generated package code to change whenever any input changes, so that Windows Installer never mistakes two different installer packages for the same one.
  3. As a release engineer, I want a generated component GUID to stay the same across versions as long as the component's key path does, so that upgrades follow Windows Installer's component rules.
  4. As a WiX author, I want GUIDs I write out to be used exactly as written, normalized to uppercase with braces, so that existing product and upgrade codes keep working.
  5. As a WiX author, I want an Id given to two elements of the same kind to be an error naming both locations, so that I find the clash quickly.

Files and source paths

  1. As a WiX author on Linux, I want a source path written with \ separators to work, so that a WiX source written on Windows builds unchanged.
  2. As a WiX author, I want source paths resolved relative to the WiX source's directory, so that the build does not depend on where zixl was run from.
  3. As a WiX author, I want a source path that does not exist to be an error naming it and the element that asked for it, so that a missing build artifact fails the build.
  4. As a WiX author on Linux, I want zixl never to fold case when looking up a source path, so that a case mismatch is reported rather than resolved differently on different machines.
  5. As a WiX author, I want long file names to get deterministic 8.3 short names, so that the installer package is valid and reproducible.
  6. As a release engineer, I want files compressed with MSZIP by default, with CompressionLevel="none" storing them uncompressed, so that I can trade size for speed.
  7. As a release engineer, I want cabinets embedded in the installer package or written as .cab files beside it, as EmbedCab says, so that both distribution styles work.
  8. As a release engineer, I want large payloads split across cabinet folders as the format requires, so that big programs still package.
  9. As a release engineer, I want several Media elements with different DiskIds, so that files can be split across cabinets.

Codepages and text

  1. As a WiX author, I want Codepage and SummaryCodepage to choose the codepage strings are stored in, defaulting to 1252, so that non-English product names display correctly.
  2. As a WiX author, I want a character the chosen codepage cannot encode to be an error naming the attribute, so that a product name is never silently mangled.
  3. As a WiX author, I want codepage 65001 accepted with a warning that Windows Installer does not support it officially, so that I can choose it knowingly.

Diagnostics

  1. As a WiX author, I want every error to give the file, line and column, so that I can jump straight to it.
  2. As a WiX author, I want an error inside an included file to say which file included it and where, so that I can tell which inclusion went wrong.
  3. As a WiX author, I want a reference to an Id that does not exist to be an error naming the missing Id, so that broken references never reach the installer package.
  4. As a WiX author, I want zixl to refuse anything that would produce an installer package Windows Installer cannot open, so that a broken package never ships.
  5. As a WiX author, I want -v to report what zixl is doing, so that I can follow a build that surprises me.

Library

  1. As a Zig developer, I want the preprocessing, compiling, linking and writing stages exposed from the zixl module, so that I can build installer packages without running a program.
  2. As a Zig developer, I want to supply files through a file source, so that my files can come from memory, a build graph or the disk.
  3. As a Zig developer, I want the output written to an Io.Writer, so that I choose where the installer package goes.
  4. As a Zig developer, I want to inspect the installer database before it is written, so that I can check or alter what is going into the installer package.
  5. As a Zig developer, I want errors returned as diagnostics with locations rather than printed, so that my tool reports them its own way.

Project

  1. As a maintainer, I want zixl's tables compared with the reference implementation's for every fixture, so that a difference in behavior is caught.
  2. As a maintainer, I want every fixture installed under Wine, so that I know Windows Installer accepts what zixl writes.
  3. As a maintainer, I want the API documentation published from the doc comments, so that library users can read it without opening the source.

Implementation Decisions

  • Four stages, each a public part of the zixl module, named as in GLOSSARY.md:
    • Preprocessing: WiX source text, defines, include directories and a file source in; preprocessed XML text out.
    • Compiling: preprocessed XML in; a WiX document out, with every reference bound, every generated GUID derived and every source path resolved.
    • Linking: a WiX document in; an installer database out.
    • Writing: an installer database and a file source in; the bytes of an installer package to an Io.Writer, plus any external cabinets to writers the caller provides.
      The command line is a thin layer over these. Nothing it does is unavailable to a library user.
  • The installer database is a format-level model: tables with typed columns and rows, plus summary information. It knows nothing about WiX, so that the M3 reader can produce the same type from an existing installer package.
  • File source: an interface asked for a source path's or include's bytes, size and timestamp. zixl never opens a file itself. The command line implements it with std.Io, tests implement it in memory, and the M2 build.zig step will implement it over LazyPaths. Source paths reach it with \ and / already normalized and relative to the WiX source's directory; case is never folded.
  • Diagnostics: every stage reports errors as a list of diagnostics, each with a file, line, column and message, plus the chain of includes that led there. The first error stops linking and writing, but compiling collects as many as it can before stopping.
  • Generated GUIDs (ADR-0003): a UUIDv5 over a hash of every input — the WiX sources after preprocessing and the contents of every source path — together with the identity of the element asking for it. Component GUIDs come from the component's key path alone. SOURCE_DATE_EPOCH supplies timestamps when set. UUIDs come from zig-uuid, ported to Zig 0.17 in a session of its own before linking is begun.
  • XML is read with zxml. Text is encoded into the database codepage with zig-charset.
  • The compound file follows [MS-CFB] version 3 (512-byte sectors), with stream names encoded the way Windows Installer encodes table and stream names. Cabinets follow [MS-CAB], with MSZIP per [MS-MCI] using std.compress.flate. LZX is not implemented.
  • Architectures set the summary information template (x64;1033, Intel;1033, Arm64;1033 and so on), the minimum installer version, and the 64-bit component attribute.
  • The command line copies wixl --help exactly for the options it implements. The executable is named zixl, with no wixl alias.
  • Clean room (ADR-0001): nothing is written from the source of msitools, wixl, libmsi or the WiX Toolset. Where the reference implementation's behavior matters, it is observed by running it.

Testing Decisions

  • Good tests check external behavior at the highest seam that can see it: what a WiX source turns into, never how a stage arrives at it. A test that breaks when internals are reorganized without changing behavior is a bad test.
  • Seam 1, WiX sources to an installer database (zig build test): most tests live here. A test gives the library WiX source text, defines, an architecture and an in-memory file source, and asserts on the resulting installer database's tables, rows and summary information, or on the diagnostics produced. Every element, attribute default and error case in the user stories gets a test here.
  • Seam 2, preprocessing (zig build test): WiX source text and defines in, preprocessed text or diagnostics out, the same output zixl -E prints. Every directive, variable kind and error case gets a test.
  • Seam 3, the zixl command end to end (NixOS VM tests, nix flake check, on nixos-do-m): for each fixture WiX source,
    • build it with zixl and with the reference implementation, dump both with msidump, and diff the tables with generated columns (GUIDs, timestamps) masked;
    • confirm msiinfo opens zixl's output;
    • install it under Wine with msiexec /i and check the files, shortcuts, registry values and environment changes landed;
    • build it again with the Windows build of zixl under Wine and require byte-identical output.
      This is the only seam that sees bytes, so the writing stage is tested here alone until the M3 reader exists. Every fixture is built for x86, x64 and arm64 where the reference implementation supports the architecture.
  • Fuzzing: the preprocessor and compiler get fuzz targets over Zig's built-in fuzzer, with test binaries built with LLVM so coverage works.
  • Prior art: zig-ftp — tests through the public module API, NixOS VM tests comparing against other implementations, fuzz targets with a checked-in corpus, test binaries with use_llvm.

Out of Scope

  • The build.zig step (M2).
  • Reading installer packages, and msiinfo- or msidump-like tools (M3).
  • WiX v4 and later sources (M4).
  • The WixUI dialog sets and any --ext extension.
  • Custom actions, services, merge modules, patches, transforms, bundles and wixl-heat.
  • LZX compression.
  • ICE validation.
  • Byte-for-byte equality with the reference implementation's output.

Further Notes

  • Blocked in part on zig-uuid supporting Zig 0.17. Preprocessing and compiling up to GUID derivation can proceed without it.
  • The specifications to work from: the Windows Installer SDK documentation (database tables, summary information, column types), [MS-CFB], [MS-CAB], [MS-MCI], and the WiX v3 documentation and XSD schema. Each is added to the project's references as it is used.
  • GLOSSARY.md and docs/adr/0001–0003 are the vocabulary and decisions this spec relies on.
## Problem Statement A Zig project that ships a Windows program wants to hand its users an `.msi` installer package, but building one today means either the WiX Toolset — a .NET program at home on Windows — or GNOME msitools' `wixl`, the reference implementation, which stands on glib, libgsf, libgcab, libxml2 and libmsi and does not build easily on Windows. Neither fits a project built with Zig and Nix on Linux CI runners, and neither produces the same bytes twice, so a Nix-built installer package is never reproducible. wixl and libmsi are also LGPL, so nothing derived from their code can become an MIT-licensed dependency of an ordinary `build.zig`. ## Solution `zixl`, a command and a Zig library that turn WiX v3 sources into installer packages on Linux or Windows, written in Zig 0.17 with no C libraries (ADR-0002). It is a clean-room reimplementation of what the reference implementation does, written from the public specifications and licensed MIT (ADR-0001). Its command line takes the same options as the reference implementation wherever zixl implements the feature behind them, so a script switches over by changing one word. Its output is reproducible: the same WiX sources and source paths always give the same bytes (ADR-0003). This spec covers milestone 1: the `zixl` command builds a real per-machine installer package for a command-line program, for x86, x64 or arm64, which installs under Windows Installer. ## User Stories ### Building an installer package 1. As a release engineer, I want to run `zixl -o app.msi app.wxs`, so that I get an installer package from a WiX source without installing the WiX Toolset or msitools. 2. As a release engineer, I want to pass several WiX sources on one command line, so that a product split across fragment files is built into one installer package. 3. As a release engineer on Linux, I want `zixl` to build installer packages, so that my CI runners never need Windows. 4. As a developer on Windows, I want the same `zixl` to run natively, so that I can build the installer package locally the way CI does. 5. As a release engineer, I want an installer package built from the same inputs to be byte-for-byte identical on every run, so that my Nix build of it is reproducible. 6. As a release engineer, I want an installer package built by the Windows build of zixl to be byte-identical to the one built on Linux, so that it does not matter which machine made a release. 7. As a release engineer, I want timestamps in the installer package to come from `SOURCE_DATE_EPOCH` when it is set, so that reproducible builds do not record the build time. 8. As a release engineer, I want the output written where `-o` says, and named the way the reference implementation names it when `-o` is left out, so that zixl behaves the way the reference implementation does. 9. As a release engineer migrating from the reference implementation, I want `-o`, `-D`, `-a`/`--arch`, `-I`/`--includedir`, `-E`, `-v` and `--version` to mean what they mean to wixl, so that my scripts work after changing one word. 10. As a release engineer migrating from the reference implementation, I want an option zixl does not implement, such as `--ext`, `--extdir` or `--wxidir`, to stop with an error saying it is not supported, so that I am never handed an installer package that silently lacks something. ### Architectures 11. As a release engineer, I want `-a x64` to produce a 64-bit installer package, so that my program installs under `Program Files` rather than `Program Files (x86)`. 12. As a release engineer, I want `-a x86` and `-a arm64` as well, so that I can ship every Windows architecture from one WiX source. 13. As a release engineer, I want x64 to be the default architecture, so that the common case needs no option. 14. As a WiX author, I want components to be marked 64-bit automatically in an x64 or arm64 installer package, so that registry values and files land in the 64-bit views. 15. As a WiX author, I want `$(sys.BUILDARCH)` to name the architecture being built, so that one WiX source can choose directories and conditions per architecture. ### Preprocessing 16. As a WiX author, I want `<?define?>` and `<?undef?>`, so that I can name values once and use them throughout. 17. As a WiX author, I want `-D NAME=VALUE` on the command line to define a variable, so that CI can pass the version number in. 18. As a WiX author, I want `$(var.NAME)`, `$(env.NAME)` and `$(sys.CURRENTDIR)`, `$(sys.SOURCEFILEDIR)` and `$(sys.SOURCEFILEPATH)`, so that sources can refer to definitions, the environment and their own location. 19. As a WiX author, I want `<?if?>`, `<?ifdef?>`, `<?ifndef?>`, `<?else?>`, `<?elseif?>` and `<?endif?>`, so that one source can vary by architecture or configuration. 20. As a WiX author, I want `<?include?>` to pull in `.wxi` files, searched for relative to the including file and then in each `-I` directory, so that shared fragments live in one place. 21. As a WiX author, I want `<?foreach?>`, so that repetitive elements can be generated from a list. 22. As a WiX author, I want `<?warning?>` and `<?error?>`, so that a source can refuse a configuration it does not support. 23. As a WiX author, I want `zixl -E` to print the preprocessed source and stop, so that I can see what my conditionals and includes produced. 24. As a WiX author, I want an undefined variable to be an error naming it and where it was used, so that a typo does not silently become an empty string. ### WiX elements 25. As a WiX author, I want `Product` and `Package` to set the product name, version, manufacturer, language, codes and package summary information, so that Windows shows my installer correctly. 26. As a WiX author, I want `Directory` and `DirectoryRef`, including the standard folders such as `ProgramFiles64Folder`, so that I can lay out where things are installed. 27. As a WiX author, I want `Component`, `ComponentRef`, `ComponentGroup` and `ComponentGroupRef`, so that I can group what is installed together and reference groups from features. 28. As a WiX author, I want `File`, with its source path, so that my program's files are put into the installer package. 29. As a WiX author, I want `Feature` and `FeatureRef`, so that components are attached to what the user installs. 30. As a WiX author, I want `Fragment`, so that parts of the installer can live in separate WiX sources and be referenced. 31. As a WiX author, I want `Property`, so that I can set Windows Installer properties. 32. As a WiX author, I want `Icon`, so that my product shows an icon in Add/Remove Programs and on shortcuts. 33. As a WiX author, I want `Shortcut`, so that my program appears in the Start menu. 34. As a WiX author, I want `RegistryKey` and `RegistryValue`, so that my program can record its install location or register itself. 35. As a WiX author, I want `Environment`, so that my command-line program's directory can be added to `PATH`. 36. As a WiX author, I want `CreateFolder` and `RemoveFolder`, so that empty directories are created and cleaned up. 37. As a WiX author, I want `MajorUpgrade`, so that installing a new version replaces the old one. 38. As a WiX author, I want `Condition` as a launch condition, so that the installer refuses to run where my program cannot. 39. As a WiX author, I want `Media`, with `Cabinet`, `EmbedCab` and `DiskId`, so that I control how files are packed. 40. As a WiX author, I want an element zixl does not support to be an error naming it and where it is, so that an installer package is never quietly missing part of what I wrote. ### Identity and GUIDs 41. As a WiX author, I want `Id="*"` on `Product` and `Package` and `Guid="*"` on `Component` to have zixl generate the GUID, so that I do not have to make them up. 42. As a release engineer, I want a generated package code to change whenever any input changes, so that Windows Installer never mistakes two different installer packages for the same one. 43. As a release engineer, I want a generated component GUID to stay the same across versions as long as the component's key path does, so that upgrades follow Windows Installer's component rules. 44. As a WiX author, I want GUIDs I write out to be used exactly as written, normalized to uppercase with braces, so that existing product and upgrade codes keep working. 45. As a WiX author, I want an `Id` given to two elements of the same kind to be an error naming both locations, so that I find the clash quickly. ### Files and source paths 46. As a WiX author on Linux, I want a source path written with `\` separators to work, so that a WiX source written on Windows builds unchanged. 47. As a WiX author, I want source paths resolved relative to the WiX source's directory, so that the build does not depend on where zixl was run from. 48. As a WiX author, I want a source path that does not exist to be an error naming it and the element that asked for it, so that a missing build artifact fails the build. 49. As a WiX author on Linux, I want zixl never to fold case when looking up a source path, so that a case mismatch is reported rather than resolved differently on different machines. 50. As a WiX author, I want long file names to get deterministic 8.3 short names, so that the installer package is valid and reproducible. 51. As a release engineer, I want files compressed with MSZIP by default, with `CompressionLevel="none"` storing them uncompressed, so that I can trade size for speed. 52. As a release engineer, I want cabinets embedded in the installer package or written as `.cab` files beside it, as `EmbedCab` says, so that both distribution styles work. 53. As a release engineer, I want large payloads split across cabinet folders as the format requires, so that big programs still package. 54. As a release engineer, I want several `Media` elements with different `DiskId`s, so that files can be split across cabinets. ### Codepages and text 55. As a WiX author, I want `Codepage` and `SummaryCodepage` to choose the codepage strings are stored in, defaulting to 1252, so that non-English product names display correctly. 56. As a WiX author, I want a character the chosen codepage cannot encode to be an error naming the attribute, so that a product name is never silently mangled. 57. As a WiX author, I want codepage 65001 accepted with a warning that Windows Installer does not support it officially, so that I can choose it knowingly. ### Diagnostics 58. As a WiX author, I want every error to give the file, line and column, so that I can jump straight to it. 59. As a WiX author, I want an error inside an included file to say which file included it and where, so that I can tell which inclusion went wrong. 60. As a WiX author, I want a reference to an `Id` that does not exist to be an error naming the missing `Id`, so that broken references never reach the installer package. 61. As a WiX author, I want zixl to refuse anything that would produce an installer package Windows Installer cannot open, so that a broken package never ships. 62. As a WiX author, I want `-v` to report what zixl is doing, so that I can follow a build that surprises me. ### Library 63. As a Zig developer, I want the preprocessing, compiling, linking and writing stages exposed from the `zixl` module, so that I can build installer packages without running a program. 64. As a Zig developer, I want to supply files through a file source, so that my files can come from memory, a build graph or the disk. 65. As a Zig developer, I want the output written to an `Io.Writer`, so that I choose where the installer package goes. 66. As a Zig developer, I want to inspect the installer database before it is written, so that I can check or alter what is going into the installer package. 67. As a Zig developer, I want errors returned as diagnostics with locations rather than printed, so that my tool reports them its own way. ### Project 68. As a maintainer, I want zixl's tables compared with the reference implementation's for every fixture, so that a difference in behavior is caught. 69. As a maintainer, I want every fixture installed under Wine, so that I know Windows Installer accepts what zixl writes. 70. As a maintainer, I want the API documentation published from the doc comments, so that library users can read it without opening the source. ## Implementation Decisions - **Four stages, each a public part of the `zixl` module**, named as in `GLOSSARY.md`: - **Preprocessing:** WiX source text, defines, include directories and a file source in; preprocessed XML text out. - **Compiling:** preprocessed XML in; a WiX document out, with every reference bound, every generated GUID derived and every source path resolved. - **Linking:** a WiX document in; an installer database out. - **Writing:** an installer database and a file source in; the bytes of an installer package to an `Io.Writer`, plus any external cabinets to writers the caller provides. The command line is a thin layer over these. Nothing it does is unavailable to a library user. - **The installer database is a format-level model**: tables with typed columns and rows, plus summary information. It knows nothing about WiX, so that the M3 reader can produce the same type from an existing installer package. - **File source**: an interface asked for a source path's or include's bytes, size and timestamp. zixl never opens a file itself. The command line implements it with `std.Io`, tests implement it in memory, and the M2 `build.zig` step will implement it over `LazyPath`s. Source paths reach it with `\` and `/` already normalized and relative to the WiX source's directory; case is never folded. - **Diagnostics**: every stage reports errors as a list of diagnostics, each with a file, line, column and message, plus the chain of `include`s that led there. The first error stops linking and writing, but compiling collects as many as it can before stopping. - **Generated GUIDs** (ADR-0003): a UUIDv5 over a hash of every input — the WiX sources after preprocessing and the contents of every source path — together with the identity of the element asking for it. Component GUIDs come from the component's key path alone. `SOURCE_DATE_EPOCH` supplies timestamps when set. UUIDs come from `zig-uuid`, ported to Zig 0.17 in a session of its own before linking is begun. - **XML** is read with `zxml`. Text is encoded into the database codepage with `zig-charset`. - **The compound file** follows [MS-CFB] version 3 (512-byte sectors), with stream names encoded the way Windows Installer encodes table and stream names. **Cabinets** follow [MS-CAB], with MSZIP per [MS-MCI] using `std.compress.flate`. LZX is not implemented. - **Architectures** set the summary information template (`x64;1033`, `Intel;1033`, `Arm64;1033` and so on), the minimum installer version, and the 64-bit component attribute. - **The command line** copies `wixl --help` exactly for the options it implements. The executable is named `zixl`, with no `wixl` alias. - **Clean room** (ADR-0001): nothing is written from the source of msitools, wixl, libmsi or the WiX Toolset. Where the reference implementation's behavior matters, it is observed by running it. ## Testing Decisions - **Good tests check external behavior at the highest seam that can see it**: what a WiX source turns into, never how a stage arrives at it. A test that breaks when internals are reorganized without changing behavior is a bad test. - **Seam 1, WiX sources to an installer database (`zig build test`):** most tests live here. A test gives the library WiX source text, defines, an architecture and an in-memory file source, and asserts on the resulting installer database's tables, rows and summary information, or on the diagnostics produced. Every element, attribute default and error case in the user stories gets a test here. - **Seam 2, preprocessing (`zig build test`):** WiX source text and defines in, preprocessed text or diagnostics out, the same output `zixl -E` prints. Every directive, variable kind and error case gets a test. - **Seam 3, the `zixl` command end to end (NixOS VM tests, `nix flake check`, on `nixos-do-m`):** for each fixture WiX source, - build it with zixl and with the reference implementation, dump both with `msidump`, and diff the tables with generated columns (GUIDs, timestamps) masked; - confirm `msiinfo` opens zixl's output; - install it under Wine with `msiexec /i` and check the files, shortcuts, registry values and environment changes landed; - build it again with the Windows build of zixl under Wine and require byte-identical output. This is the only seam that sees bytes, so the writing stage is tested here alone until the M3 reader exists. Every fixture is built for x86, x64 and arm64 where the reference implementation supports the architecture. - **Fuzzing**: the preprocessor and compiler get fuzz targets over Zig's built-in fuzzer, with test binaries built with LLVM so coverage works. - **Prior art**: zig-ftp — tests through the public module API, NixOS VM tests comparing against other implementations, fuzz targets with a checked-in corpus, test binaries with `use_llvm`. ## Out of Scope - The `build.zig` step (M2). - Reading installer packages, and `msiinfo`- or `msidump`-like tools (M3). - WiX v4 and later sources (M4). - The WixUI dialog sets and any `--ext` extension. - Custom actions, services, merge modules, patches, transforms, bundles and `wixl-heat`. - LZX compression. - ICE validation. - Byte-for-byte equality with the reference implementation's output. ## Further Notes - Blocked in part on `zig-uuid` supporting Zig 0.17. Preprocessing and compiling up to GUID derivation can proceed without it. - The specifications to work from: the Windows Installer SDK documentation (database tables, summary information, column types), [MS-CFB], [MS-CAB], [MS-MCI], and the WiX v3 documentation and XSD schema. Each is added to the project's references as it is used. - `GLOSSARY.md` and `docs/adr/0001`–`0003` are the vocabulary and decisions this spec relies on.
jeff closed this issue 2026-10-14 09:51:11 -05:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jeff/zixl#1
No description provided.