Developed from both Windows and Linux, so nothing in the build should care which host it runs on. It did, in four places. Toolchain: one path instead of two. clang is a cross-compiler by construction, so a single LLVM install targets bare-metal ELF, 16/32-bit code below 1 MB, and Linux/musl userland from any host -- which is how the Windows build already worked. The GNU branch beside it had drifted into passing --no-warn-rwx-segments, a hard error on lld, which is the default linker on both FreeBSD and macOS; and it required a brewed x86_64-elf-gcc before anything built on a Mac at all. GNU is kept as a fallback (USE_GNU=1) but clang+lld is chosen whenever both are present. Host tools use the host's own cc; only mingw needs naming explicitly. tools/mkschfs.c included <sys/sysmacros.h> unconditionally on non-mingw hosts. That header is glibc/musl only -- macOS and the BSDs have neither the header nor a need for it, since major()/minor() come from <sys/types.h> there. Now guarded on __linux__. Ports verified tarballs with `sha256sum -c -`, which does not exist on macOS (shasum), FreeBSD/NetBSD (sha256), or OpenBSD (cksum -a sha256), and each spells its check mode differently. tools/sha256check.sh picks whichever of the five the host has and compares digests itself. nproc is coreutils, so absent on macOS and BSD; falls through to sysctl -n hw.ncpu before defaulting to 4. The PowerShell tools now run under pwsh on any OS: QEMU is found on PATH (the scoop location is a Windows-only fallback), and path literals use forward slashes -- Join-Path $root 'build\x' produces a *filename containing a backslash* on a Unix host. The backslash sweep is limited to known directory prefixes on purpose: bootcheck sends printf 'caf\303\251' to the guest to test UTF-8, and a blanket substitution would have quietly changed what that asserts. Also fixes six tools left booting build/fs.img, which stopped being bootable when SchFS became the root filesystem: crashshot, dispshot, fbshot, splashshot, textshot and ttyshot all take build/schfs.img now, and the -SchFS switches that used to select it are gone. Verified on Windows: full build, and bootcheck green. The Linux, macOS and BSD paths are written from their documented behaviour and are NOT executed here -- this machine has no WSL and no second host.
200 lines
8.8 KiB
PowerShell
200 lines
8.8 KiB
PowerShell
# How much of SchOS is actually SchOS.
|
|
#
|
|
# `wc -l` over the whole tree answers a question nobody asked: it counts musl,
|
|
# toybox, mksh and a NetBSD checkout, which between them are
|
|
# two orders of magnitude larger than this project and none of them ours. This
|
|
# counts only first-party code, and separates the code from the comments so the
|
|
# number means something.
|
|
#
|
|
# tools/loc.ps1 the summary table
|
|
# tools/loc.ps1 -PerFile every file, largest first
|
|
# tools/loc.ps1 -All include third-party trees, for comparison
|
|
#
|
|
# Comment detection is deliberately simple: it handles // and /* */ for C and
|
|
# # for scripts, and does not try to understand a // inside a string literal.
|
|
# That is worth being honest about -- the comment ratio is indicative, not
|
|
# exact. The line and byte counts are exact.
|
|
|
|
param(
|
|
[switch]$PerFile,
|
|
[switch]$All,
|
|
[int]$Top = 30
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$root = Split-Path $PSScriptRoot -Parent
|
|
|
|
# What counts as ours. Everything else in the tree is either somebody else's
|
|
# source or generated output, and mixing either in makes the total meaningless.
|
|
$areas = [ordered]@{
|
|
'kernel' = 'kernel'
|
|
'include' = 'include'
|
|
'boot' = 'boot'
|
|
'lib' = 'lib'
|
|
'cmd' = 'cmd'
|
|
'tools' = 'tools'
|
|
'docs' = 'docs'
|
|
'projects' = 'projects'
|
|
}
|
|
if ($All) {
|
|
$areas['ports (3rd party)'] = 'ports'
|
|
$areas['src-trunk (NetBSD)'] = 'src-trunk'
|
|
}
|
|
|
|
# Source only. Images, fonts-as-data and upstream tarballs are not code.
|
|
$exts = @('.c', '.h', '.S', '.s', '.ld', '.inc', '.ps1', '.sh', '.pl', '.py', '.md', '.mk')
|
|
|
|
function Measure-Area([string]$rel) {
|
|
$dir = Join-Path $root $rel
|
|
$res = [PSCustomObject]@{
|
|
Area = $rel; Files = 0; Lines = 0; Code = 0; Comment = 0; Blank = 0
|
|
Bytes = 0; NonWs = 0; Alpha = 0; Digit = 0; Files_ = @()
|
|
}
|
|
if (-not (Test-Path $dir)) { return $res }
|
|
|
|
Get-ChildItem -Path $dir -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object {
|
|
$f = $_
|
|
if ($exts -notcontains $f.Extension.ToLower()) { return }
|
|
# Makefiles have no extension; picked up separately below.
|
|
$text = [System.IO.File]::ReadAllText($f.FullName)
|
|
$lines = $text -split "`r?`n"
|
|
# A trailing newline produces one empty element; do not count it.
|
|
if ($lines.Length -gt 0 -and $lines[-1] -eq '') { $lines = $lines[0..($lines.Length-2)] }
|
|
|
|
$code = 0; $comment = 0; $blank = 0
|
|
$inBlock = $false
|
|
$isC = @('.c','.h','.s','.ld','.inc') -contains $f.Extension.ToLower()
|
|
foreach ($l in $lines) {
|
|
$t = $l.Trim()
|
|
if ($t -eq '') { $blank++; continue }
|
|
if ($inBlock) {
|
|
$comment++
|
|
if ($t -match '\*/') { $inBlock = $false }
|
|
continue
|
|
}
|
|
if ($isC) {
|
|
if ($t.StartsWith('//')) { $comment++; continue }
|
|
if ($t.StartsWith('/*')) {
|
|
$comment++
|
|
if ($t -notmatch '\*/') { $inBlock = $true }
|
|
continue
|
|
}
|
|
} elseif ($t.StartsWith('#') -and $f.Extension -ne '.md') {
|
|
$comment++; continue
|
|
}
|
|
$code++
|
|
}
|
|
|
|
$nonWs = ($text.ToCharArray() | Where-Object { -not [char]::IsWhiteSpace($_) }).Count
|
|
$alpha = ($text.ToCharArray() | Where-Object { [char]::IsLetter($_) }).Count
|
|
$digit = ($text.ToCharArray() | Where-Object { [char]::IsDigit($_) }).Count
|
|
|
|
$res.Files++
|
|
$res.Lines += $lines.Length
|
|
$res.Code += $code
|
|
$res.Comment += $comment
|
|
$res.Blank += $blank
|
|
$res.Bytes += $f.Length
|
|
$res.NonWs += $nonWs
|
|
$res.Alpha += $alpha
|
|
$res.Digit += $digit
|
|
$res.Files_ += [PSCustomObject]@{
|
|
Path = $f.FullName.Substring($root.Length + 1)
|
|
Lines = $lines.Length; Code = $code; Comment = $comment
|
|
}
|
|
}
|
|
return $res
|
|
}
|
|
|
|
$results = @()
|
|
foreach ($k in $areas.Keys) { $results += (Measure-Area $areas[$k]) }
|
|
|
|
# The top-level Makefile has no extension, so it is added by hand rather than
|
|
# widening the extension list and dragging in every stray file in the tree.
|
|
$mk = Join-Path $root 'Makefile'
|
|
if (Test-Path $mk) {
|
|
$mkLines = ([System.IO.File]::ReadAllText($mk) -split "`r?`n")
|
|
$r = $results | Where-Object { $_.Area -eq 'tools' } | Select-Object -First 1
|
|
if ($r) {
|
|
$r.Files++; $r.Lines += $mkLines.Length; $r.Bytes += (Get-Item $mk).Length
|
|
$r.Code += ($mkLines | Where-Object { $_.Trim() -ne '' -and -not $_.Trim().StartsWith('#') }).Count
|
|
$r.Comment += ($mkLines | Where-Object { $_.Trim().StartsWith('#') }).Count
|
|
$r.Blank += ($mkLines | Where-Object { $_.Trim() -eq '' }).Count
|
|
}
|
|
}
|
|
|
|
$fmt = @(
|
|
@{ N='Area'; E={$_.Area} }
|
|
@{ N='Files'; E={$_.Files}; A='right' }
|
|
@{ N='Lines'; E={'{0:N0}' -f $_.Lines}; A='right' }
|
|
@{ N='Code'; E={'{0:N0}' -f $_.Code}; A='right' }
|
|
@{ N='Comment'; E={'{0:N0}' -f $_.Comment}; A='right' }
|
|
@{ N='Blank'; E={'{0:N0}' -f $_.Blank}; A='right' }
|
|
@{ N='Cmt%'; E={ if ($_.Code + $_.Comment) { '{0:N0}%' -f (100 * $_.Comment / ($_.Code + $_.Comment)) } else { '-' } }; A='right' }
|
|
@{ N='Ln/File'; E={ if ($_.Files) { '{0:N0}' -f ($_.Lines / $_.Files) } else { '-' } }; A='right' }
|
|
@{ N='KiB'; E={'{0:N0}' -f ($_.Bytes / 1KB)}; A='right' }
|
|
)
|
|
|
|
Write-Host ''
|
|
Write-Host ' SchOS — first-party source' -ForegroundColor Cyan
|
|
Write-Host ''
|
|
Write-Host (($results | Where-Object { $_.Files -gt 0 } | Sort-Object Lines -Descending | Format-Table $fmt -AutoSize | Out-String))
|
|
|
|
# Two totals, because they answer different questions and conflating them
|
|
# flatters the number. "The OS" is what boots and runs on the machine; docs,
|
|
# host-side tools and the projects/ stubs are first-party but are not SchOS
|
|
# running on hardware. Linux 0.01's 10,239 lines were kernel, headers and boot
|
|
# -- no documentation and no build tooling -- so only the first total is a fair
|
|
# comparison.
|
|
$osAreas = @('kernel','include','boot','lib','cmd')
|
|
$own = $results | Where-Object { $_.Area -notin @('ports','src-trunk') }
|
|
$os = $results | Where-Object { $_.Area -in $osAreas }
|
|
$T = ($own | Measure-Object -Property Lines -Sum).Sum
|
|
$C = ($own | Measure-Object -Property Code -Sum).Sum
|
|
$M = ($own | Measure-Object -Property Comment -Sum).Sum
|
|
$B = ($own | Measure-Object -Property Blank -Sum).Sum
|
|
$F = ($own | Measure-Object -Property Files -Sum).Sum
|
|
$By = ($own | Measure-Object -Property Bytes -Sum).Sum
|
|
$NW = ($own | Measure-Object -Property NonWs -Sum).Sum
|
|
$AL = ($own | Measure-Object -Property Alpha -Sum).Sum
|
|
$DG = ($own | Measure-Object -Property Digit -Sum).Sum
|
|
|
|
Write-Host (' {0,-22} {1,12:N0}' -f 'Files', $F)
|
|
Write-Host (' {0,-22} {1,12:N0}' -f 'Lines total', $T)
|
|
Write-Host (' {0,-22} {1,12:N0} {2:N0}%' -f ' of which code', $C, (100 * $C / [Math]::Max($T,1)))
|
|
Write-Host (' {0,-22} {1,12:N0} {2:N0}%' -f ' of which comment', $M, (100 * $M / [Math]::Max($T,1)))
|
|
Write-Host (' {0,-22} {1,12:N0} {2:N0}%' -f ' of which blank', $B, (100 * $B / [Math]::Max($T,1)))
|
|
Write-Host (' {0,-22} {1,12:N0}' -f 'Lines per file', ($T / [Math]::Max($F,1)))
|
|
Write-Host ''
|
|
$osT = ($os | Measure-Object -Property Lines -Sum).Sum
|
|
$osC = ($os | Measure-Object -Property Code -Sum).Sum
|
|
$osF = ($os | Measure-Object -Property Files -Sum).Sum
|
|
Write-Host (' {0,-22} {1,12:N0} ({2:N0} code, {3} files)' -f 'THE OS ITSELF', $osT, $osC, $osF) -ForegroundColor Green
|
|
Write-Host (' {0,-22}' -f ' kernel+include+boot+lib+cmd') -ForegroundColor DarkGray
|
|
Write-Host ''
|
|
Write-Host (' {0,-22} {1,12:N0}' -f 'Bytes', $By)
|
|
Write-Host (' {0,-22} {1,12:N0}' -f 'Non-whitespace chars', $NW)
|
|
Write-Host (' {0,-22} {1,12:N0}' -f ' letters', $AL)
|
|
Write-Host (' {0,-22} {1,12:N0}' -f ' digits', $DG)
|
|
Write-Host ''
|
|
|
|
# For scale. Linux 0.01 (September 1991) is 10,239 lines by the same crude
|
|
# measure -- every line of every file in the tarball, comments included.
|
|
$lin = 10239
|
|
Write-Host (' For scale: Linux 0.01 was {0:N0} lines (kernel, headers, boot -- the' -f $lin) -ForegroundColor DarkGray
|
|
Write-Host (' same things counted above as THE OS ITSELF). SchOS is {0:N1}x that.' -f ($osT / $lin)) -ForegroundColor DarkGray
|
|
Write-Host ''
|
|
|
|
if ($PerFile) {
|
|
Write-Host ' Largest files' -ForegroundColor Cyan
|
|
# Out-String, not a bare Format-Table: two Format-Table calls writing to
|
|
# the same output stream interleave their grouping records and PowerShell
|
|
# rejects the second one outright.
|
|
$rows = ($own | ForEach-Object { $_.Files_ }) |
|
|
Sort-Object Lines -Descending | Select-Object -First $Top |
|
|
Format-Table @{N='Lines';E={'{0:N0}' -f $_.Lines};A='right'},
|
|
@{N='Code'; E={'{0:N0}' -f $_.Code};A='right'},
|
|
@{N='Cmt'; E={'{0:N0}' -f $_.Comment};A='right'},
|
|
Path -AutoSize | Out-String
|
|
Write-Host $rows
|
|
}
|