Sacrilege—Linux From Scratch PowerShell Script

This isn’t my only questionable take. After I switched back to WSL1, because I disabled virtualization after I found out Windows does insane things when Hyper-V is enabled, I revisited Linux From Scratch since I don’t like any of the pre-packaged WSL1 distros. I asked myself “What scripting language should I write it in?”

I settled on PowerShell.

Yes, you read that right. I actually really like PowerShell. Its syntax isn’t pretty, but it’s effective. Because it’s backed by .NET, it’s very capable out of the box. It’s cross-platform. Building a Linux system with PowerShell feels like blasphemy, but hey, it works quite well.

So far I’ve gotten through the base cross-compiled development tools. My current hurdles are handling case-insensitivity that is causing glibc to choke and some path substitutions.

#Requires -Version 7.5

[CmdletBinding(DefaultParameterSetName="StagingPhase")]
param (
	[string]$WorkingDirectory=(Join-Path -Path ${PSScriptRoot} -ChildPath "lfs"),
	[string]$Jobs="1",
	[string]$LFSVersion="12.4",
	[string]$BinutilsVersion="2.45",
	[string]$MPFRVersion="4.2.2",
	[string]$GMPVersion="6.3.0",
	[string]$MPCVersion="1.3.1",
	[string]$GCCVersion="15.2.0",
	[string]$GLIBCVersion="2.42",
	[string]$LinuxVersion="6.16.1",

	[Parameter(ParameterSetName="StagingPhase")]
	[switch]$StagingPhase,
	[switch]$Binutils,
	[switch]$GCC,
	[switch]$LinuxHeaders,
	[switch]$GLIBC,
	[switch]$LibStdCPP,
	[switch]$All,
	[Parameter(ParameterSetName="PopulationPhase")]
	[switch]$PopulationPhase
)

#Set-PSDebug -Trace 1
# FIXME: fsutil.exe file setCaseSensitiveInfo D:\src\lfs enable
Get-Command -ErrorAction Stop -Type Application "gcc"
Get-Command -ErrorAction Stop -Type Application "bison"

$env:LFS=$WorkingDirectory
$SourcesDirectory=(Join-Path -Path $WorkingDirectory -ChildPath "sources")
New-Item -Path $WorkingDirectory -ItemType Directory -Force
New-Item -Path $SourcesDirectory -ItemType Directory -Force

$env:LC_ALL="POSIX"
$env:LFS_TGT="$(uname -m)-lfs-linux-gnu"
$env:PATH="${env:LFS}/tools/bin:/usr/bin"
$env:CONFIG_SITE="${env:LFS}/usr/share/config.site"
umask 022

function Stage {
	Push-Location $WorkingDirectory

	New-Item -ItemType Directory -Path "etc", "usr", "var" -Force
	Push-Location (Join-Path -Path $WorkingDirectory -ChildPath "usr")
	New-Item -ItemType Directory -Path "bin", "lib", "lib64", "sbin" -Force
	Pop-Location

	foreach ($installPath in @("bin", "lib", "sbin")) {
		ln -sv (Join-Path -Path "usr" -ChildPath $installPath) (Join-Path -Path $WorkingDirectory -ChildPath $installPath)
	}

	# binutils
	if ($Binutils -or $All) {
		$BinutilsDirectory="binutils-${BinutilsVersion}"
		$BinutilsPackageName="${BinutilsDirectory}.tar.xz"
		$BinutilsURL="https://sourceware.org/pub/binutils/releases/${BinutilsPackageName}"
		$BinutilsFilePath=(Join-Path -Path $SourcesDirectory -ChildPath $BinutilsPackageName)
		if (-not (Test-Path -Path $BinutilsFilePath)) {
			Invoke-WebRequest -Uri $BinutilsURL -OutFile $BinutilsFilePath
		}
		Push-Location $SourcesDirectory
		if (-not (Test-Path -Path $BinutilsDirectory)) {
			tar -xvf $BinutilsPackageName
		}
		Push-Location $BinutilsDirectory
		$BuildDirectory="build"
		New-Item -ItemType Directory -Path $BuildDirectory -Force
		Push-Location $BuildDirectory
		$Arguments=@(
			"--prefix=${env:LFS}/tools",
			"--with-sysroot=${env:LFS}",
			"--target=${env:LFS_TGT}",
			"--disable-nls",
			"--enable-gprofng=no",
			"--disable-werror",
			"--enable-new-dtags",
			"--enable-default-hash-style=gnu"
		)
		Start-Process -FilePath "../configure" -ArgumentList $Arguments -Wait -NoNewWindow
		Start-Process -FilePath "make" -ArgumentList @("-j", $Jobs) -Wait -NoNewWindow
		make install
		Pop-Location
		Pop-Location
		Remove-Item -Path $BinutilsDirectory -Recurse -Force
		Remove-Item -Path $BinutilsPackageName
		Pop-Location
	}

	# gcc
	if ($GCC -or $All) {
		$GCCDirectory="gcc-${GCCVersion}"
		$GCCPackageName="${GCCDirectory}.tar.xz"
		$GCCURL="https://ftp.gnu.org/gnu/gcc/gcc-${GCCVersion}/${GCCPackageName}"
		$GCCFilePath=(Join-Path -Path $SourcesDirectory -ChildPath $GCCPackageName)
		if (-not (Test-Path -Path $GCCFilePath)) {
			Invoke-WebRequest -Uri $GCCURL -OutFile $GCCFilePath
		}
		Push-Location $SourcesDirectory
		if (-not (Test-Path -Path $GCCDirectory)) {
			tar -xvf $GCCPackageName
		}
		Push-Location $GCCDirectory
		$MPFRDirectory="mpfr-${MPFRVersion}"
		$MPFRPackageName="${MPFRDirectory}.tar.xz"
		$MPFRURL="https://ftp.gnu.org/gnu/mpfr/${MPFRPackageName}"
		Invoke-WebRequest -Uri $MPFRURL -OutFile $MPFRPackageName
		tar -xvf $MPFRPackageName
		mv $MPFRDirectory mpfr
		$GMPDirectory="gmp-${GMPVersion}"
		$GMPPackageName="${GMPDirectory}.tar.xz"
		$GMPURL="https://ftp.gnu.org/gnu/gmp/${GMPPackageName}"
		Invoke-WebRequest -Uri $GMPURL -OutFile $GMPPackageName
		tar -xvf $GMPPackageName
		mv $GMPDirectory gmp
		$MPCDirectory="mpc-${MPCVersion}"
		$MPCPackageName="${MPCDirectory}.tar.gz"
		$MPCURL="https://ftp.gnu.org/gnu/mpc/${MPCPackageName}"
		Invoke-WebRequest -Uri $MPCURL -OutFile $MPCPackageName
		tar -xvf $MPCPackageName
		mv $MPCDirectory mpc
		$BuildDirectory="build"
		New-Item -ItemType Directory -Path $BuildDirectory -Force
		Start-Process -FilePath "sed" -ArgumentList @("-e", "/m64=/s/lib64/lib/", "-i.orig", "gcc/config/i386/t-linux64") -Wait -NoNewWindow
		Push-Location $BuildDirectory
		$Arguments=@(
			"--target=${env:LFS_TGT}",
			"--prefix=${env:LFS}/tools",
			"--with-glibc-version=${GLIBCVersion}"
			"--with-sysroot=${env:LFS}",
			"--with-newlib",
			"--without-headers",
			"--enable-default-pie",
			"--enable-default-ssp",
			"--disable-nls",
			"--disable-shared",
			"--disable-multilib",
			"--disable-threads",
			"--disable-libatomic",
			"--disable-libgomp",
			"--disable-libquadmath",
			"--disable-libssp",
			"--disable-libvtv",
			"--disable-libstdcxx",
			"--enable-languages=c,c++"
		)
		Start-Process -FilePath "../configure" -ArgumentList $Arguments -Wait -NoNewWindow
		Start-Process -FilePath "make" -ArgumentList @("-j", $Jobs) -Wait -NoNewWindow
		make install
		Pop-Location
		Push-Location gcc
		$LibGCCFileName=&${env:LFS_TGT}-gcc -print-libgcc-file-name
		$IncludeDirectory=(Split-Path -Path $LibGCCFileName -Parent)
		foreach ($File in @("limitx.h", "glimits.h", "limity.h")) {
			Get-Content -Path $File | Add-Content -Path "$IncludeDirectory/include/limits.h"
		}
		Pop-Location
		Pop-Location
		Remove-Item -Path $GCCDirectory -Recurse -Force
		Remove-Item -Path $GCCPackageName
		Pop-Location
	}

	# linux headers
	if ($LinuxHeaders -or $All) {
		$LinuxDirectory="linux-${LinuxVersion}"
		$LinuxPackageName="${LinuxDirectory}.tar.xz"
		$LinuxURL="https://www.kernel.org/pub/linux/kernel/v$(([version]$LinuxVersion).major).x/${LinuxPackageName}"
		$LinuxFilePath=(Join-Path -Path $SourcesDirectory -ChildPath $LinuxPackageName)
		if (-not (Test-Path -Path $LinuxFilePath)) {
			Invoke-WebRequest -Uri $LinuxURL -OutFile $LinuxFilePath
		}
		Push-Location $SourcesDirectory
		if (-not (Test-Path -Path $LinuxDirectory)) {
			tar -xvf $LinuxPackageName
		}
		Push-Location $LinuxDirectory
		make mrproper
		make headers
		&find usr/include -type f ! -name '*.h' -delete
		&cp -rv usr/include ${env:LFS}/usr
		Pop-Location
		Remove-Item -Path $LinuxDirectory -Recurse -Force
		Pop-Location
	}

	# glibc
	if ($GLIBC -or $All) {
		$GLIBCDirectory="glibc-${GLIBCVersion}"
		$GLIBCPackageName="${GLIBCDirectory}.tar.xz"
		$GLIBCURL="https://ftp.gnu.org/gnu/glibc/${GLIBCPackageName}"
		$GLIBCFilePath=(Join-Path -Path $SourcesDirectory -ChildPath $GLIBCPackageName)
		if (-not (Test-Path -Path $GLIBCFilePath)) {
			Invoke-WebRequest -Uri $GLIBCURL -OutFile $GLIBCFilePath
		}
		Push-Location $SourcesDirectory
		if (-not (Test-Path -Path $GLIBCDirectory)) {
			tar -xvf $GLIBCPackageName
		}
		Push-Location $GLIBCDirectory
		&ln -sfv ../lib/ld-linux-x86-64.so.2 ${env:LFS}/lib64
		&ln -sfv ../lib/ld-linux-x86-64.so.2 ${env:LFS}/lib64/ld-lsb-x86-64.so.3
		$FHSPatchFile="glibc-${GLIBCVersion}-fhs-1.patch"
		Invoke-WebRequest -Uri "https://www.linuxfromscratch.org/patches/lfs/${LFSVersion}/${FHSPatchFile}" -OutFile ${FHSPatchFile}
		&patch -Np1 -i glibc-2.42-fhs-1.patch
		Add-Content -Path "configparms" -Value "rootsbindir=/usr/sbin"
		$BuildDirectory="build"
		New-Item -ItemType Directory -Path $BuildDirectory -Force
		Push-Location $BuildDirectory
		$Arguments=@(
			"--prefix=/usr",
			"--host=${env:LFS_TGT}",
			"--build=$(../scripts/config.guess)",
			"--disable-nscd",
			"libc_cv_slibdir=/usr/lib",
			"--enable-kernel=5.4"
		)
		Start-Process -FilePath "../configure" -ArgumentList $Arguments -Wait -NoNewWindow
		Start-Process -FilePath "make" -ArgumentList @("-j", $Jobs) -Wait -NoNewWindow
		&make DESTDIR=${env:LFS} install
		Pop-Location
		Pop-Location
		Remove-Item -Path $GLIBCDirectory -Recurse -Force
		Remove-Item -Path $GLIBCPackageName -Recurse
		Pop-Location
	}

	if ($LibStdCPP -or $All) {
		$GCCDirectory="gcc-${GCCVersion}"
		$GCCPackageName="${GCCDirectory}.tar.xz"
		$GCCURL="https://ftp.gnu.org/gnu/gcc/gcc-${GCCVersion}/${GCCPackageName}"
		$GCCFilePath=(Join-Path -Path $SourcesDirectory -ChildPath $GCCPackageName)
		if (-not (Test-Path -Path $GCCFilePath)) {
			Invoke-WebRequest -Uri $GCCURL -OutFile $GCCFilePath
		}
		Push-Location $SourcesDirectory
		if (-not (Test-Path -Path $GCCDirectory)) {
			tar -xvf $GCCPackageName
		}
		Push-Location $GCCDirectory
		$BuildDirectory="build"
		New-Item -ItemType Directory -Path $BuildDirectory -Force
		Push-Location $BuildDirectory
		$Arguments=@(
			"--host=${env:LFS_TGT}",
			"--build=$(../config.guess)",
			"--prefix=/usr",
			"--disable-multilib",
			"--disable-nls",
			"--disable-libstdcxx-pch",
			"--with-gxx-include-dir=/tools/${env:LFS_TGT}/include/c++/${GCCVersion}"
		)
		Start-Process -FilePath "../libstdc++-v3/configure" -ArgumentList $Arguments -Wait -NoNewWindow
		Start-Process -FilePath "make" -ArgumentList @("-j", $Jobs) -Wait -NoNewWindow
		&make DESTDIR=${env:LFS} install
		Start-Process -FilePath "rm" -ArgumentList @("-v", "${env:LFS}/usr/lib/lib{stdc++{,exp,fs},supc++}.la") -Wait -NoNewWindow # FIXME: this is not being interpreted as bash
		Pop-Location
		Pop-Location
		Pop-Location
	}

	Pop-Location
}

function Populate {
}

switch ($PsCmdlet.ParameterSetName) {
	"StagingPhase" {
		Stage
	}
	"PopulationPhase" {
		Populate
	}
}

Code language: PowerShell (powershell)

Leave a Reply

Your email address will not be published. Required fields are marked *