Merge pull request #1488 from Sonicadvance1/python_auto_install

Scripts: Adds a python script that can hand hold a user through FEX install
This commit is contained in:
Ryan Houdek 2022-01-03 19:07:41 -08:00 committed by GitHub
commit b276750c8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 414 additions and 0 deletions

View File

@ -4,10 +4,25 @@ It has native support for a rootfs overlay, so you don't need to chroot, as well
FEX presents a Linux 5.0 interface to the guest, and supports both AArch64 and x86-64 as hosts.
FEX is very much work in progress, so expect things to change.
## Quick start guide
### For Ubuntu 20.04, 21.04, 21.10, 22.04
Execute the following command in the terminal to install FEX through a PPA.
`curl --silent https://raw.githubusercontent.com/FEX-Emu/FEX/main/Scripts/InstallFEX.py | python3`
This command will walk you through installing FEX through a PPA, and downloading a RootFS for use with FEX.
Ubuntu PPA is updated with our monthly releases.
### For everyone else
Follow the guide on the official FEX-Emu Wiki [Here](https://wiki.fex-emu.org/index.php/QuickStartGuide)
## Getting Started
FEX has been tested to build and run on ARMv8.0, ARMv8.1+, and x86-64(AVX or newer) hardware.
ARMv7 and older x86 hardware will not work.
Expected operating system usage is Linux. FEX has been tested with Ubuntu 20.04, 20.10, and 21.04. Also Arch Linux.
On AArch64 hosts the user **MUST** have an x86-64 RootFS [Creating a RootFS](#RootFS-Generation).
### Navigating the Source

399
Scripts/InstallFEX.py Executable file
View File

@ -0,0 +1,399 @@
#!/usr/bin/python3
import os
import subprocess
import sys
import tempfile
_Arch = None
def GetArch():
global _Arch
if _Arch == None:
_Arch = subprocess.check_output(['uname', '-m']).decode("utf-8").strip()
return _Arch
_Distro = None
def GetDistro():
global _Distro
# Query files in order
# /etc/lsb-release
# /etc/os-release
if _Distro == None:
if os.path.exists("/etc/lsb-release"):
File = open("/etc/lsb-release", "r")
Lines = File.readlines()
File.close()
Found = 0
Distro = ""
Version = ""
for Line in Lines:
Key, Val = Line.split("=", 1)
if Key == "DISTRIB_ID":
Distro = Val.strip().lower()
Found+=1
if Key == "DISTRIB_RELEASE":
Version = Val.strip()
Found+=1
if Found == 2:
_Distro = [Distro, Version]
return _Distro
if os.path.exists("/etc/os-release"):
File = open("/etc/os-release", "r")
Lines = File.readlines()
File.close()
Found = 0
Distro = ""
Version = ""
for Line in Lines:
Key, Val = Line.split("=", 1)
if Key == "ID":
Distro = Val.strip()
Found+=1
if Key == "VERSION_ID":
# Strip the double quotes from the version id
Version = Val.strip()[1:-1]
Found+=1
if Found == 2:
_Distro = [Distro, Version]
return _Distro
# Unknown
_Distro = ["Unknown", "0.0"]
return _Distro
def IsSupportedArch():
Arch = GetArch()
return Arch == "aarch64"
def IsSupportedDistro():
Distro = GetDistro()
# We only support Ubuntu
if Distro[0] == "ubuntu":
# We only support what is available in ppa:fex-emu/fex
return Distro[1] == "20.04" or \
Distro[1] == "21.04" or \
Distro[1] == "21.10" or \
Distro[1] == "22.04"
return False
_ArchVersion = None
def ListContainsRequired(Features, RequiredFeatures):
for Req in RequiredFeatures:
if not Req in Features:
return False
return True
def GetCPUFeaturesVersion():
global _ArchVersion
# Also LOR but kernel doesn't expose this
v8_1Mandatory = ["atomics", "asimdrdm", "crc32"]
v8_2Mandatory = v8_1Mandatory + ["dcpop"]
v8_3Mandatory = v8_2Mandatory + ["fcma", "jscvt", "lrcpc", "paca", "pacg"]
v8_4Mandatory = v8_3Mandatory + ["asimddp", "flagm", "ilrcpc", "uscat"]
# fphp asimdhp asimddp
if _ArchVersion == None:
File = open("/proc/cpuinfo", "r")
Lines = File.readlines()
File.close()
# Minimum spec is ARMv8.0
_ArchVersion = "8.0"
for Line in Lines:
if "Features" in Line:
Features = Line.split(":")[1].strip().split(" ")
# We don't care beyond 8.4 right now
if ListContainsRequired(Features, v8_4Mandatory):
_ArchVersion = "8.4"
elif ListContainsRequired(Features, v8_3Mandatory):
_ArchVersion = "8.3"
elif ListContainsRequired(Features, v8_2Mandatory):
_ArchVersion = "8.2"
elif ListContainsRequired(Features, v8_1Mandatory):
_ArchVersion = "8.1"
break;
return _ArchVersion
_PPAInstalled = None
FEXPPA = "http://ppa.launchpad.net/fex-emu/fex/ubuntu"
def GetPPAStatus():
global _PPAInstalled
if _PPAInstalled == None:
_PPAInstalled = False
CacheResults = subprocess.check_output(['apt-cache', 'policy']).decode("utf-8")
for Line in CacheResults.split("\n"):
if "http" in Line:
Line = Line.strip()
LineSplit = Line.split(" ")
# 'status' 'URL' 'series' 'arch' 'type'
if LineSplit[1] == FEXPPA:
_PPAInstalled = True
break
return _PPAInstalled
def InstallPPA():
print ("Installing PPA: ppa:fex-emu/fex")
print ("This bit will ask for your password")
DidInstall = False
try:
CmdResult = subprocess.call(["sudo", "add-apt-repository", "-y", "ppa:fex-emu/fex"])
DidInstall = CmdResult == 0
except KeyboardInterrupt:
DidInstall = False
pass
if DidInstall:
print("PPA installed")
else:
print("PPA failed to install")
return DidInstall
ARMVersionToPackage = {
"8.0": "fex-emu-armv8.0",
"8.1": "fex-emu-armv8.0",
"8.2": "fex-emu-armv8.2",
"8.3": "fex-emu-armv8.2",
"8.4": "fex-emu-armv8.4",
}
def GetPackagesToInstall():
return [
ARMVersionToPackage[GetCPUFeaturesVersion()],
"fex-emu-binfmt32",
"fex-emu-binfmt64",
]
def UpdatePPA():
print ("Updating apt sources")
print ("This bit will ask for your password")
DidUpdate = False
try:
CmdResult = subprocess.call(["sudo", "apt-get", "update"])
DidUpdate = CmdResult == 0
except KeyboardInterrupt:
DidUpdate = False
pass
if DidUpdate:
print("PPA installed")
else:
print("PPA failed to install")
return DidUpdate
def CheckAndInstallPackageUpdates():
PackagesToInstall = GetPackagesToInstall()
for Package in PackagesToInstall[:]:
UpgradableStatus = subprocess.check_output(["apt", "list", "--upgradable", Package]).decode("utf-8")
Found = False
for Line in UpgradableStatus.split("\n"):
# If the package exists to be upgraded then it will appear in this list
# We need to check multiple lines
# $ apt list --upgradable <Package>
# With upgrade available
# Listing... Done
# <Package>/<Repo> <NewVersion> <arch> [upgradable from: <Installed version>]
# Without upgrade available
# Listing... Done
# <EOF>
if Package in Line and "upgradable" in Line:
Found = True
if Found == False:
PackagesToInstall.remove(Package)
if len(PackagesToInstall) > 0:
print ("Found updates for packages: {}".format(PackagesToInstall))
print ("This bit may ask for your password")
DidInstall = False
try:
CmdResult = subprocess.call(["sudo", "apt-get", "-y", "install"] + PackagesToInstall)
DidInstall = CmdResult == 0
except KeyboardInterrupt:
print ("Keyboard interrupt")
DidInstall = False
pass
if DidInstall:
print("Packages updated")
else:
print("Packages failed to update")
return DidInstall
return True
def CheckPackageInstallStatus():
PackagesToInstall = GetPackagesToInstall()
for Package in PackagesToInstall[:]:
CmdResult = subprocess.call(["dpkg", "-s", Package], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if CmdResult == 0:
PackagesToInstall.remove(Package)
return PackagesToInstall
def InstallPackages(Packages):
print("Installing packages: {}".format(Packages))
DidInstall = False
try:
CmdResult = subprocess.call(["sudo", "apt-get", "-y", "install"] + Packages)
DidInstall = CmdResult == 0
except KeyboardInterrupt:
print ("Keyboard interrupt")
DidInstall = False
pass
if DidInstall:
print("Packages installed")
else:
print("Packages failed to install")
return DidInstall
_RootFSPath = None
def GetRootFSPath():
global _RootFSPath
if _RootFSPath == None:
# Follows the same logic as FEXCore::Config::GetDataDirectory()
HomeDir = os.getenv("HOME")
if HomeDir == None:
HomeDir = os.getenv("PWD")
if HomeDir == None:
HomeDir = "."
Path = HomeDir
DataXDG = os.getenv("XDG_DATA_HOME")
if DataXDG != None:
Path = DataXDG
Path = Path + "/.fex-emu"
DataOverride = os.getenv("FEX_APP_DATA_LOCATION")
if DataOverride != None:
Path = DataOverride
_RootFSPath = Path + "/RootFS/"
return _RootFSPath
def CheckRootFSInstallStatus():
# Matches what is available on https://rootfs.fex-emu.org/file/fex-rootfs/RootFS_links.txt
UbuntuVersionToRootFS = {
"20.04": "Ubuntu_21_04.sqsh",
"21.04": "Ubuntu_21_04.sqsh",
"21.10": "Ubuntu_21_10.sqsh",
"22.04": "Ubuntu_21_10.sqsh",
}
return os.path.exists(GetRootFSPath() + UbuntuVersionToRootFS[GetDistro()[1]])
def TryInstallRootFS():
DidInstall = False
try:
CmdResult = subprocess.call(["FEXRootFSFetcher"])
DidInstall = CmdResult == 0
except KeyboardInterrupt:
print ("Keyboard interrupt")
DidInstall = False
pass
return DidInstall
def TryBasicProgramExecution():
return subprocess.call(["FEXInterpreter", "/usr/bin/uname", "-a"]) == 0
def ExitWithStatus(Status):
# Remove the cached credentials
subprocess.call(["sudo", "-K"])
sys.exit(Status)
def main():
# Only run on supported arch
if not IsSupportedArch():
print ( "{} is not a supported architecture".format(GetArch()))
ExitWithStatus(-1)
if not IsSupportedDistro():
Distro = GetDistro()
print ( "'{} {}' is not a supported distro".format(Distro[0], Distro[1]))
ExitWithStatus(-1)
if GetDistro()[0] == "ubuntu":
print ("Getting PPA status: {}".format(("NotInstalled", "Installed")[GetPPAStatus()]))
if GetPPAStatus():
if not UpdatePPA():
print ("apt sources failed to update. Not continuing")
ExitWithStatus(-1)
if not CheckAndInstallPackageUpdates():
print ("apt packages failed to update. Not continuing")
ExitWithStatus(-1)
else:
if not InstallPPA():
print ("PPA failed to install. Not continuing")
ExitWithStatus(-1)
Packages = CheckPackageInstallStatus()
if len(Packages) > 0:
if not InstallPackages(Packages):
print ("Failed to install packages. Not continuing")
ExitWithStatus(-1)
if not CheckRootFSInstallStatus():
print ("RootFS not found. Running FEXRootFSFetcher to get rootfs")
if not TryInstallRootFS():
print ("Failed to install RootFS. Not continuing")
ExitWithStatus(-1)
print ("FEX is now installed. Trying basic program run")
if not TryBasicProgramExecution():
print ("FEXInterpreter failed to run. Not continuing")
ExitWithStatus(-1)
print ("")
print ("===================================================")
print ("FEX test run executed. You should be set to run FEX")
print ("===================================================")
print ("Usage examples:")
print ("# steam is a bash script. Wrap with FEXBash")
print ("\tFEXBash steam")
print ("# Full path execution execution will wrap the application if it exists in the rootfs")
print ("\tFEXInterpreter /usr/bin/uname")
print ("# Freestanding x86/x86-64 programs can be executed directly. binfmt_misc will redirect to FEX")
print ("\t$HOME/PetalCrashOnline.AppImage")
print ("# If you need a terminal that emulates everything.")
print ("# Run FEXBash without arguments. Double check uname to see if running under FEX")
print ("\tFEXBash")
ExitWithStatus(0)
if __name__ == "__main__":
sys.exit(main())