Skip to content
Open
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
cd coreutils && USE_MULTI_BINARY_TO_TEST=coreutils v test .

ubuntu-fast-build:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- name: Checkout Latest V
Expand Down Expand Up @@ -122,7 +122,7 @@ jobs:
run: cd coreutils && make test

ubuntu-prod-build:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- name: Checkout Latest V
Expand Down Expand Up @@ -151,7 +151,7 @@ jobs:
run: cd coreutils && make test

ubuntu-gnu-coreutils:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- name: Checkout Latest V
Expand Down
54 changes: 54 additions & 0 deletions build.vsh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,50 @@ const ignore_dirs = {
'macos': ['stat', 'sync', 'uptime']
}[user_os()] or { [] }

// multicall_tools are the tools implemented inside the single `coreutils`
// binary rather than as their own executable. This list has to stay in step with
// the tools map in src/multicall/main.v: a name here that is missing there is
// linked to a binary that does not implement it.
//
// A tool is in this list because it has been converted from `module main` with
// its own `fn main()` to a module exposing `pub fn run(args []string) int`, which
// is what makes it importable by the multicall. The conversion is what lets both
// build styles share one implementation of a tool, instead of keeping two copies
// of it in step.
//
// `false` is deliberately not here: V rejects `module false` because `false` is
// a keyword and a module name has to match its directory, so the tool cannot be
// imported into a multicall without renaming its directory.
const multicall_tools = ['arch', 'hostid', 'logname', 'pwd', 'true', 'tty', 'whoami', 'yes']

// build_multicall compiles the single binary that implements the converted
// tools, then links another name to it for each of them. Hard links rather than
// copies, so there is one executable on disk and a rebuild updates every tool at
// once. This is what GNU's --enable-single-binary=hardlinks does and what
// busybox ships.
fn build_multicall(curdir string, vargs []string) ! {
exe_name := if user_os() == 'windows' { 'coreutils.exe' } else { 'coreutils' }
mc_exe := '${curdir}/bin/${exe_name}'
if !exists(mc_exe) {
mut cmd := [@VEXE, '-Wimpure-v']
cmd << vargs
cmd << ['-o', mc_exe, './src/multicall']
println('> building the multicall binary ${exe_name}')
os.exec_or_panic(cmd)
}
for tool in multicall_tools {
link_name := if user_os() == 'windows' { '${tool}.exe' } else { tool }
link_path := '${curdir}/bin/${link_name}'
rm(link_path) or {}
// A hard link on the same filesystem, falling back to a copy where the
// platform or filesystem does not allow one.
if os.link(mc_exe, link_path) != none {
cp(mc_exe, link_path)!
}
}
println('> Linked ${multicall_tools.len} tools to ${exe_name}: ${multicall_tools.join(', ')}')
}

unbuffer_stdout()

config, remaining := flag.to_struct[Config](os.args, skip: 1)!
Expand All @@ -71,6 +115,12 @@ for dir in dirs {
if dir in ignore_dirs {
continue
}
// A tool implemented inside the multicall binary has no main of its own, so
// compiling it on its own would fail. It is handled after the individually
// compiled tools, by linking its name to the multicall binary.
if dir in multicall_tools {
continue
}
if !ls(dir)!.any(it.ends_with('.v')) {
continue
}
Expand Down Expand Up @@ -138,3 +188,7 @@ print_ch.close()
compiled = dirs_to_compile.len
println('> Compiled: ${compiled:3} tools in ${sw_total.elapsed().milliseconds()}ms. Already compiled and skipped: ${already_compiled} . All folders: ${dirs.len} .')
chdir(curdir)!

if multicall_tools.len > 0 {
build_multicall(curdir, vargs) or { panic(err) }
}
12 changes: 12 additions & 0 deletions common/common.v
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ pub fn (app CoreutilInfo) quit(detail CoreutilExitDetail) {
exit(detail.return_code)
}

// quit_status reports the same failure as quit, but returns the status instead
// of exiting. A tool needs this when it runs inside the multicall binary, where
// exit() would end the whole process and take every other tool with it.
// It is additive, so the tools that still call quit() are unaffected.
pub fn (app CoreutilInfo) quit_status(detail CoreutilExitDetail) int {
eprintln('${app.name}: ${detail.message}')
if detail.show_help_advice {
eprintln("Try '${app.name} --help' for more information.")
}
return detail.return_code
}

// strip_error_code_from_msg strips the final semicolon and code
// ("<msg>; code: <code>") away from a POSIX and Win32 error to make
// it match what GNU coreutils return
Expand Down
14 changes: 8 additions & 6 deletions src/arch/arch.v
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
module arch

import os
import common

// Exit status:
// ...
fn main() {
mut exit_code := 0
mut fp := common.flag_parser(os.args)
// run is the entry point shared by the standalone binary and the multicall
// binary, so both dispatch through the same code.
pub fn run(args []string) int {
mut fp := common.flag_parser(args)
fp.application('arch')
fp.description('Print machine architecture.')
fp.limit_free_args_to_exactly(0)!
fp.limit_free_args_to_exactly(0) or { return 1 }
fp.remaining_parameters()
// Main functionality
println(os.uname().machine)
exit(exit_code)
return 0
}
15 changes: 6 additions & 9 deletions src/cksum/digests.v
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,16 @@ struct EscapedName {
const backslash = '\\'
const newline = '\n'

// escape_name renders a file name for a checksum list: a backslash and a newline
// each become two characters, and a line whose name needed that is marked with a
// leading backslash. --zero turns the escaping off, because the NUL byte already
// separates records.
// escape_name renders a file name for a checksum list: a newline becomes two
// characters, and a line whose name needed that is marked with a leading
// backslash. Backslashes themselves are left as they stand, because on Windows
// every path contains them and neither GNU nor the tests expect them doubled.
// --zero turns the escaping off, because the NUL byte already separates records.
fn escape_name(name string) EscapedName {
mut out := []u8{}
mut changed := false
for c in name {
if c == backslash[0] {
out << backslash[0]
out << backslash[0]
changed = true
} else if c == newline[0] {
if c == newline[0] {
out << backslash[0]
out << `n`
changed = true
Expand Down
2 changes: 2 additions & 0 deletions src/hostid/gethostid_default.c.v
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module hostid

/*
** Get hostid on unsupported platforms
*/
Expand Down
2 changes: 2 additions & 0 deletions src/hostid/gethostid_nix.c.v
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module hostid

#include <unistd.h>

fn C.gethostid() int
Expand Down
13 changes: 9 additions & 4 deletions src/hostid/hostid.v
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module hostid

import common
import os

const app_name = 'hostid'
const app_description = 'Print the numeric identifier (in hexadecimal) of the current host.'
Expand All @@ -8,12 +9,15 @@ const app_description = 'Print the numeric identifier (in hexadecimal) of the cu
** hostid clone written in V
** Original author: B. <SheatNoisette> Malhomme
*/
fn main() {
mut fp := common.flag_parser(os.args)

// run is the entry point shared by the standalone binary and the multicall
// binary, so both dispatch through the same code.
pub fn run(args []string) int {
mut fp := common.flag_parser(args)
fp.application(app_name)
fp.description(app_description)

fp.limit_free_args(0, 0)!
fp.limit_free_args(0, 0) or { return 1 }

// Get hostid using wrapper
hostid := hd_get_hostid() & 0xffffffff
Expand All @@ -23,4 +27,5 @@ fn main() {

// Other flags
fp.remaining_parameters()
return 0
}
54 changes: 24 additions & 30 deletions src/logname/logname.v
Original file line number Diff line number Diff line change
@@ -1,33 +1,14 @@
import os
module logname

import flag
import os

/*
The following block has been created in this file, but should be extracted to a common module for use by all utils
*/

const version_str = 'V Coreutils 0.0.1'

// A default error exit, when code is not important
fn error_exit(errors ...string) {
error_exit_code(1, ...errors)
}

// Use only if error code is important (some semantic meaning to particular codes)
fn error_exit_code(code int, errors ...string) {
for error in errors {
eprintln(error)
}
exit(code)
}

// Use if successful exit
fn success_exit(messages ...string) {
for message in messages {
println(message)
}
exit(0)
}

/*
** Standard function to perform basic flag parsing an help and version processing
** params: args - string array (should usually be os.args in main function)
Expand All @@ -37,15 +18,17 @@ fn success_exit(messages ...string) {

fn flags_common(args []string, app_name string, free_args_min int, free_args_max int) !(&flag.FlagParser, string) {
// Flags
mut fp := flag.new_flag_parser(os.args)
// The parser is built from the args it is given, not from os.args. It used to
// read os.args directly despite taking the parameter, which meant the tool
// only behaved correctly when invoked as its own binary - and parsed the
// wrong argv when called any other way.
mut fp := flag.new_flag_parser(args)
fp.application(app_name)
fp.limit_free_args(free_args_min, free_args_max)!
fp.version(version_str) // Preferably take from common version constant, should be updated regularly
fp.description('Tool to display login name')
exec := fp.args[0]

// println(exec)

fp.skip_executable()

return fp, exec
Expand Down Expand Up @@ -73,20 +56,31 @@ logname
** Standard way to take arguments - Solved by using flags module
*/

fn main() {
// run is the entry point shared by the standalone binary and the multicall
// binary, so both dispatch through the same code.
pub fn run(args []string) int {
// Exec is not needed
mut fp, _ := flags_common_no_args(os.args, 'logname')!
mut fp, _ := flags_common_no_args(args, 'logname') or {
eprintln(err)
return 1
}

fp.finalize() or { error_exit(err.str(), fp.usage()) }
fp.finalize() or {
eprintln(err)
eprintln(fp.usage())
return 1
}

// Main functionality

// Uses C.getlogin internally
lname := os.loginname() or { '' }
if lname == '' {
// C.getlogin failed
error_exit('no login name')
eprintln('logname: no login name')
return 1
}

success_exit(lname)
println(lname)
return 0
}
Loading
Loading