build: make all 76 utilities compile against current V - #195
Merged
Merged
Conversation
main does not build with V 0.5.2, and `make testfmt` is red too, so every
CI job has been failing. This gets both gates back to green without changing
any intended behaviour.
Compile fixes, one per cause:
- `v.mathutil` is gone; `min`/`max` now live in `math`. Affects basenc, cut
and tail.
- `for ; cond; post {}` and `for i := n; i; i-- {}` are no longer accepted:
the condition has to be `bool`. Affects cksum and expand.
- `time.sleep` takes a `time.Duration`, not a number of seconds. sleep now
converts explicitly and saturates instead of overflowing, so `sleep inf`
keeps waiting rather than wrapping around to roughly zero.
- `const` cannot hold the result of a function call, so printenv's NUL
terminator became a function.
- `setup_cp_command`/`setup_mv_command`/`setup_command` (head) were declared
with `?`, but every failure path in them calls the @[noreturn]
`common.exit_with_error_message`, so they never return an error and their
callers had no `err` to look at. They now return plain tuples.
- `C.statvfs` is already declared privately by vlib/builtin/cfns.c.v, so a
local declaration resolved to that one and failed to compile. stat binds a
distinct name to the libc call with #define.
- `struct utmpx` was declared twice, in common/readutmp_nix.c.v and again in
src/users/users.c.v with a different, shorter field list, which V now
rejects as a redeclaration. Declared once, with the full layout.
stat additionally needed more than a compile fix:
- src/stat/stat_to_vlib.v (now src/stat/modes.v) imported v.scanner and
v.pref to decode --printf escapes. Building it pulled the whole V compiler
into the program: `v build` of src/stat never finished, and taking the
machine down with it. The escapes are now decoded directly.
- `<bits/statx.h>` is a glibc internal header, so it is absent on musl based
distributions (issue vlang#145). The kernel uapi `<linux/stat.h>` is used instead.
- `statx()` wrote the kernel's 224 byte struct through a `voidptr` into a
V struct that was 160 bytes, so every call corrupted memory past the end of
the allocation; stat segfaulted on every invocation. The C struct is now
declared and used for real. `struct statvfs` has the same problem and its
V mirror now matches the ABI including the trailing spare.
- `$embed_file` no longer resolves inside a function body, so fstypes.txt is
embedded at module scope.
wc needed a fix of its own:
- `read_chunk` returned `?FileChunk`, but its caller inspected `err`, which
does not exist for an option. os.File.read also reports end of file as an
`os.Eof` error, and reading a zero-length chunk indexed `buffer[-1]`, so
`wc` on an empty file would have panicked. It now returns `!FileChunk` and
distinguishes exhaustion from a real read error.
- `\r` was matched both as "ignore" and as whitespace, which V rejects as a
duplicate case.
- `byte(bool)` is no longer a valid conversion; the single-count check counts
the selected options instead.
Finally, `v fmt -w .` over the 16 files that `v fmt -verify` rejected.
With this, `make` builds all 76 utilities and `make testfmt` passes.
This was referenced Oct 2, 2026
JalonSolov
pushed a commit
that referenced
this pull request
Oct 4, 2026
…#196) * build: make all 76 utilities compile against current V main does not build with V 0.5.2, and `make testfmt` is red too, so every CI job has been failing. This gets both gates back to green without changing any intended behaviour. Compile fixes, one per cause: - `v.mathutil` is gone; `min`/`max` now live in `math`. Affects basenc, cut and tail. - `for ; cond; post {}` and `for i := n; i; i-- {}` are no longer accepted: the condition has to be `bool`. Affects cksum and expand. - `time.sleep` takes a `time.Duration`, not a number of seconds. sleep now converts explicitly and saturates instead of overflowing, so `sleep inf` keeps waiting rather than wrapping around to roughly zero. - `const` cannot hold the result of a function call, so printenv's NUL terminator became a function. - `setup_cp_command`/`setup_mv_command`/`setup_command` (head) were declared with `?`, but every failure path in them calls the @[noreturn] `common.exit_with_error_message`, so they never return an error and their callers had no `err` to look at. They now return plain tuples. - `C.statvfs` is already declared privately by vlib/builtin/cfns.c.v, so a local declaration resolved to that one and failed to compile. stat binds a distinct name to the libc call with #define. - `struct utmpx` was declared twice, in common/readutmp_nix.c.v and again in src/users/users.c.v with a different, shorter field list, which V now rejects as a redeclaration. Declared once, with the full layout. stat additionally needed more than a compile fix: - src/stat/stat_to_vlib.v (now src/stat/modes.v) imported v.scanner and v.pref to decode --printf escapes. Building it pulled the whole V compiler into the program: `v build` of src/stat never finished, and taking the machine down with it. The escapes are now decoded directly. - `<bits/statx.h>` is a glibc internal header, so it is absent on musl based distributions (issue #145). The kernel uapi `<linux/stat.h>` is used instead. - `statx()` wrote the kernel's 224 byte struct through a `voidptr` into a V struct that was 160 bytes, so every call corrupted memory past the end of the allocation; stat segfaulted on every invocation. The C struct is now declared and used for real. `struct statvfs` has the same problem and its V mirror now matches the ABI including the trailing spare. - `$embed_file` no longer resolves inside a function body, so fstypes.txt is embedded at module scope. wc needed a fix of its own: - `read_chunk` returned `?FileChunk`, but its caller inspected `err`, which does not exist for an option. os.File.read also reports end of file as an `os.Eof` error, and reading a zero-length chunk indexed `buffer[-1]`, so `wc` on an empty file would have panicked. It now returns `!FileChunk` and distinguishes exhaustion from a real read error. - `\r` was matched both as "ignore" and as whitespace, which V rejects as a duplicate case. - `byte(bool)` is no longer a valid conversion; the single-count check counts the selected options instead. Finally, `v fmt -w .` over the 16 files that `v fmt -verify` rejected. With this, `make` builds all 76 utilities and `make testfmt` passes. * fix: repair the 10 test files that were failing for unrelated reasons Follow-up to #195, which got the build and the formatting gate green. This brings `make test` from 37/47 to 46/47. The only remaining failure is shuf_test.v, which is issue #189 and gets its own PR. ## common/pwd: group ids came back as garbage getgrouplist() and getgroups() fill an array of gid_t, which is 32 bits wide, but the V declarations handed them a []int, i.e. i64. The kernel wrote the low half of each slot and left the upper half holding the previous entry, so gids arrived as 103079215108 (24 * 2^32 + 4, i.e. adm's 4 with cdrom's 24 above it). The two functions now fill a []u32. getgrgid() was also declared as returning `&C.passwd` rather than `&C.group`. That happened to work because gr_name sits at the same offset as pw_name, but gr_mem/gr_gid do not line up, so there is now a proper struct. groups and id now agree with GNU for the plain, named-user, -nG and -G forms. ## tail: KiB was decimal, and PB/EB used the binary multiplier The suffix table mapped both `kb` and `kib` to 1000, both `mb` and `mib` to 1000^2, and so on, so `tail -c 1KiB` read a third of what it should have. `petabyte` was also `terra * kilo` and `exabyte` `peta * kilo`, i.e. the binary multipliers, instead of continuing the decimal chain. The two chains are now separate and correct, and the test asserts GNU's actual values rather than mirroring the constants. The overflow check was `result == 0 && number != 0`, which cannot catch a wrapped product; it now checks that the division comes back out. `20E` is rejected like GNU rejects it. Z/Y/R/Q stay unsupported, because although GNU's help mentions them, GNU itself rejects all of them with "Value too large for defined data type" (checked against 9.4). Verified end to end against GNU for 2b, k, K, KB, KiB, M, MB, MiB, G, GB, GiB, T, TB, TiB, P, PB, PiB, E, EB and EiB, plus the rejection cases. ## numfmt: 2000.0 and 2e+06 f64.str() always keeps a fractional part and switches to scientific notation above a million, so `--to=none 2000000` printed `2e+06` and `2K` printed `2000.0`. Both now go through a helper based on strlong() that drops a trailing `.0`. ## stat: the default Device: field The default format used `%Dh/%dd`, which printed the device number in hex over the same number in decimal: `830h/2096d` where GNU prints `8,48`. GNU's default is `Device: %Hd,%Ld`, and %H/%L are two character tokens that scan_for_tokens() did not handle; there was already a TODO about it. Both tokens are implemented and the default format matches. ## Three tests were asserting the wrong thing, or crashing * `tail_test` and `fmt_test` captured `&result` for a local in `setup()` and returned the closure. Once setup() returned, the pointer dangled and the closure appended to freed stack memory: `malloc_uninit(0xd0a07408218d1b1f < 0)`. The array header is now heap allocated. (cut_test and paste_test use the same shape but call the closure in scope, so they are fine.) * `string_to_i64_test` used `!` on an option return, which does not compile. * `sum_test` had `const util` next to `import io.util`. The import is aliased. * `truncate_test` computed its expectations as `24 * 1000 * 1000 * 1000`. V's `int` is 32-bit, so the expectation wrapped to -1769803776 while the value under test, a u64, was right all along. The expected values are now u64. ## Testing `v run build.vsh` builds all 76 utilities, `v fmt -verify .` passes, and `v test .` reports 46 of 47 files passing, up from 37.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a prerequisite for everything else:
maincurrently does not build, so everyCI job has been red since 2025-12-21 (last green run before that was the
Timeout implementationmerge).9 of the 76 utilities failed to compile, and
v fmt -verify .rejected 14 files.With this change all 76 build and
make testfmtpasses.Compile fixes
v.mathutilremoved;min/maxmoved tomathfor ; cond; post {}/for i := n; i; i-- {}now need aboolconditiontime.sleeptakes atime.Durationconstcannot hold a function result?(...)returns yield noerr, but the callers read oneC.statvfsalready declared privately byvlib/builtin/cfns.c.vstruct utmpxdeclared twice with different layouts$embed_fileno longer resolves inside a function bodyEach is a one-line-or-so change with no behaviour change. Two deserve comment:
sleeppreviously wrotetime.sleep(seconds * time.second). The conversion isnow explicit and saturates at
max_duration, sosleep infkeeps waitinginstead of overflowing to roughly zero.
cp/mv/headbuild their settings with?, but every failure path in thosefunctions calls the
@[noreturn]common.exit_with_error_message. They cannever return an error, so the callers'
or { ... err.msg() }was dead code thatcould not compile. They now return plain tuples.
stat
statneeded more than a compile fix.It hung the compiler.
stat_to_vlib.v(nowmodes.v) importedv.scannerandv.prefto decode--printfescapes. Buildingsrc/statpulled in the whole V compiler:
v buildnever finished, and it took themachine down with it. Measured directly:
The escapes are now decoded by a small purpose-built routine, which also picks
up the
\NNNand\xHHforms GNU supports and the old version silentlydropped.
It segfaulted on every call.
statx()wrote the kernel's 224-bytestruct statxthrough avoidptrinto a V struct of 160 bytes, so the writeran past the end of the allocation.
struct statvfshas the same problem; itsV mirror is now the full ABI including the trailing spare. The C structs are
declared and used for real now.
<bits/statx.h>is glibc-internal, so it is missing on musl. This isissue #145; the kernel uapi
<linux/stat.h>is used instead.Spot-checked against GNU coreutils 9.4:
wc
read_chunkreturned?FileChunkwhile its caller inspectederr, which doesnot exist for an option.
os.File.readalso signals end of file with anos.Eoferror, and a zero-length read indexedbuffer[-1], sowcon anempty file would have panicked. It now returns
!FileChunkand separatesexhaustion from a real read error. Also:
\rwas matched twice in the samematch, andbyte(bool)is no longer a valid conversion.Output now matches GNU for empty files, stdin, every count flag, multi-file
column alignment, and the missing-file error.
Formatting
v fmt -w .over the 16 filesv fmt -verifyrejected.Known remaining failures
make teststill fails, in 10 files. These are pre-existing logic bugs, notbuild regressions, and each is independent:
groups_nix_test.v,id_nix_test.vrootfour timesnumfmt_test.v2Krenders2000.0;--to=nonegives2e+06truncate_test.v24Goverflows to-1769803776(32-bit size)shuf_test.v--random-sourcedoes not reproduce GNU's permutationstat_nix_test.vfmt_test.v,tail_test.v,string_to_i64_test.v,sum_test.vI would rather land the build fix on its own and take these as separate PRs, so
each is reviewable. Happy to keep going in this branch instead if you prefer.
Testing
v run build.vsh— all 76 utilities build, 0 failuresmake testfmt— passesmake test— 37/47 test files pass (was: 9 utilities could not build at all,so their tests could not run at all)
Verified on V 0.5.2 (0dc6a69, upstream
vlang/vmaster) on Ubuntu 24.04 withGNU coreutils 9.4, which is the same V the CI checks out.