PlatformBuild.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. # @file
  2. # Script to Build ArmVirtPkg UEFI firmware
  3. #
  4. # Copyright (c) Microsoft Corporation.
  5. # SPDX-License-Identifier: BSD-2-Clause-Patent
  6. ##
  7. import os
  8. import logging
  9. import io
  10. from edk2toolext.environment import shell_environment
  11. from edk2toolext.environment.uefi_build import UefiBuilder
  12. from edk2toolext.invocables.edk2_platform_build import BuildSettingsManager
  13. from edk2toolext.invocables.edk2_setup import SetupSettingsManager, RequiredSubmodule
  14. from edk2toolext.invocables.edk2_update import UpdateSettingsManager
  15. from edk2toolext.invocables.edk2_pr_eval import PrEvalSettingsManager
  16. from edk2toollib.utility_functions import RunCmd
  17. from edk2toollib.utility_functions import GetHostInfo
  18. # ####################################################################################### #
  19. # Common Configuration #
  20. # ####################################################################################### #
  21. class CommonPlatform():
  22. ''' Common settings for this platform. Define static data here and use
  23. for the different parts of stuart
  24. '''
  25. PackagesSupported = ("ArmVirtPkg",)
  26. ArchSupported = ("AARCH64", "ARM")
  27. TargetsSupported = ("DEBUG", "RELEASE", "NOOPT")
  28. Scopes = ('armvirt', 'edk2-build')
  29. WorkspaceRoot = os.path.realpath(os.path.join(
  30. os.path.dirname(os.path.abspath(__file__)), "..", ".."))
  31. # ####################################################################################### #
  32. # Configuration for Update & Setup #
  33. # ####################################################################################### #
  34. class SettingsManager(UpdateSettingsManager, SetupSettingsManager, PrEvalSettingsManager):
  35. def GetPackagesSupported(self):
  36. ''' return iterable of edk2 packages supported by this build.
  37. These should be edk2 workspace relative paths '''
  38. return CommonPlatform.PackagesSupported
  39. def GetArchitecturesSupported(self):
  40. ''' return iterable of edk2 architectures supported by this build '''
  41. return CommonPlatform.ArchSupported
  42. def GetTargetsSupported(self):
  43. ''' return iterable of edk2 target tags supported by this build '''
  44. return CommonPlatform.TargetsSupported
  45. def GetRequiredSubmodules(self):
  46. ''' return iterable containing RequiredSubmodule objects.
  47. If no RequiredSubmodules return an empty iterable
  48. '''
  49. rs = []
  50. # intentionally declare this one with recursive false to avoid overhead
  51. rs.append(RequiredSubmodule(
  52. "CryptoPkg/Library/OpensslLib/openssl", False))
  53. # To avoid maintenance of this file for every new submodule
  54. # lets just parse the .gitmodules and add each if not already in list.
  55. # The GetRequiredSubmodules is designed to allow a build to optimize
  56. # the desired submodules but it isn't necessary for this repository.
  57. result = io.StringIO()
  58. ret = RunCmd("git", "config --file .gitmodules --get-regexp path", workingdir=self.GetWorkspaceRoot(), outstream=result)
  59. # Cmd output is expected to look like:
  60. # submodule.CryptoPkg/Library/OpensslLib/openssl.path CryptoPkg/Library/OpensslLib/openssl
  61. # submodule.SoftFloat.path ArmPkg/Library/ArmSoftFloatLib/berkeley-softfloat-3
  62. if ret == 0:
  63. for line in result.getvalue().splitlines():
  64. _, _, path = line.partition(" ")
  65. if path is not None:
  66. if path not in [x.path for x in rs]:
  67. rs.append(RequiredSubmodule(path, True)) # add it with recursive since we don't know
  68. return rs
  69. def SetArchitectures(self, list_of_requested_architectures):
  70. ''' Confirm the requests architecture list is valid and configure SettingsManager
  71. to run only the requested architectures.
  72. Raise Exception if a list_of_requested_architectures is not supported
  73. '''
  74. unsupported = set(list_of_requested_architectures) - \
  75. set(self.GetArchitecturesSupported())
  76. if(len(unsupported) > 0):
  77. errorString = (
  78. "Unsupported Architecture Requested: " + " ".join(unsupported))
  79. logging.critical(errorString)
  80. raise Exception(errorString)
  81. self.ActualArchitectures = list_of_requested_architectures
  82. def GetWorkspaceRoot(self):
  83. ''' get WorkspacePath '''
  84. return CommonPlatform.WorkspaceRoot
  85. def GetActiveScopes(self):
  86. ''' return tuple containing scopes that should be active for this process '''
  87. scopes = CommonPlatform.Scopes
  88. ActualToolChainTag = shell_environment.GetBuildVars().GetValue("TOOL_CHAIN_TAG", "")
  89. if GetHostInfo().os.upper() == "LINUX" and ActualToolChainTag.upper().startswith("GCC"):
  90. if "AARCH64" in self.ActualArchitectures:
  91. scopes += ("gcc_aarch64_linux",)
  92. if "ARM" in self.ActualArchitectures:
  93. scopes += ("gcc_arm_linux",)
  94. return scopes
  95. def FilterPackagesToTest(self, changedFilesList: list, potentialPackagesList: list) -> list:
  96. ''' Filter other cases that this package should be built
  97. based on changed files. This should cover things that can't
  98. be detected as dependencies. '''
  99. build_these_packages = []
  100. possible_packages = potentialPackagesList.copy()
  101. for f in changedFilesList:
  102. # BaseTools files that might change the build
  103. if "BaseTools" in f:
  104. if os.path.splitext(f) not in [".txt", ".md"]:
  105. build_these_packages = possible_packages
  106. break
  107. # if the azure pipeline platform template file changed
  108. if "platform-build-run-steps.yml" in f:
  109. build_these_packages = possible_packages
  110. break
  111. return build_these_packages
  112. def GetPlatformDscAndConfig(self) -> tuple:
  113. ''' If a platform desires to provide its DSC then Policy 4 will evaluate if
  114. any of the changes will be built in the dsc.
  115. The tuple should be (<workspace relative path to dsc file>, <input dictionary of dsc key value pairs>)
  116. '''
  117. return (os.path.join("ArmVirtPkg", "ArmVirtQemu.dsc"), {})
  118. # ####################################################################################### #
  119. # Actual Configuration for Platform Build #
  120. # ####################################################################################### #
  121. class PlatformBuilder(UefiBuilder, BuildSettingsManager):
  122. def __init__(self):
  123. UefiBuilder.__init__(self)
  124. def AddCommandLineOptions(self, parserObj):
  125. ''' Add command line options to the argparser '''
  126. parserObj.add_argument('-a', "--arch", dest="build_arch", type=str, default="AARCH64",
  127. help="Optional - Architecture to build. Default = AARCH64")
  128. def RetrieveCommandLineOptions(self, args):
  129. ''' Retrieve command line options from the argparser '''
  130. shell_environment.GetBuildVars().SetValue(
  131. "TARGET_ARCH", args.build_arch.upper(), "From CmdLine")
  132. shell_environment.GetBuildVars().SetValue(
  133. "ACTIVE_PLATFORM", "ArmVirtPkg/ArmVirtQemu.dsc", "From CmdLine")
  134. def GetWorkspaceRoot(self):
  135. ''' get WorkspacePath '''
  136. return CommonPlatform.WorkspaceRoot
  137. def GetPackagesPath(self):
  138. ''' Return a list of workspace relative paths that should be mapped as edk2 PackagesPath '''
  139. return ()
  140. def GetActiveScopes(self):
  141. ''' return tuple containing scopes that should be active for this process '''
  142. scopes = CommonPlatform.Scopes
  143. ActualToolChainTag = shell_environment.GetBuildVars().GetValue("TOOL_CHAIN_TAG", "")
  144. Arch = shell_environment.GetBuildVars().GetValue("TARGET_ARCH", "")
  145. if GetHostInfo().os.upper() == "LINUX" and ActualToolChainTag.upper().startswith("GCC"):
  146. if "AARCH64" == Arch:
  147. scopes += ("gcc_aarch64_linux",)
  148. elif "ARM" == Arch:
  149. scopes += ("gcc_arm_linux",)
  150. return scopes
  151. def GetName(self):
  152. ''' Get the name of the repo, platform, or product being build '''
  153. ''' Used for naming the log file, among others '''
  154. # check the startup nsh flag and if set then rename the log file.
  155. # this helps in CI so we don't overwrite the build log since running
  156. # uses the stuart_build command.
  157. if(shell_environment.GetBuildVars().GetValue("MAKE_STARTUP_NSH", "FALSE") == "TRUE"):
  158. return "ArmVirtPkg_With_Run"
  159. return "ArmVirtPkg"
  160. def GetLoggingLevel(self, loggerType):
  161. ''' Get the logging level for a given type
  162. base == lowest logging level supported
  163. con == Screen logging
  164. txt == plain text file logging
  165. md == markdown file logging
  166. '''
  167. return logging.DEBUG
  168. def SetPlatformEnv(self):
  169. logging.debug("PlatformBuilder SetPlatformEnv")
  170. self.env.SetValue("PRODUCT_NAME", "ArmVirtQemu", "Platform Hardcoded")
  171. self.env.SetValue("MAKE_STARTUP_NSH", "FALSE", "Default to false")
  172. self.env.SetValue("QEMU_HEADLESS", "FALSE", "Default to false")
  173. return 0
  174. def PlatformPreBuild(self):
  175. return 0
  176. def PlatformPostBuild(self):
  177. return 0
  178. def FlashRomImage(self):
  179. VirtualDrive = os.path.join(self.env.GetValue(
  180. "BUILD_OUTPUT_BASE"), "VirtualDrive")
  181. os.makedirs(VirtualDrive, exist_ok=True)
  182. OutputPath_FV = os.path.join(
  183. self.env.GetValue("BUILD_OUTPUT_BASE"), "FV")
  184. Built_FV = os.path.join(OutputPath_FV, "QEMU_EFI.fd")
  185. # pad fd to 64mb
  186. with open(Built_FV, "ab") as fvfile:
  187. fvfile.seek(0, os.SEEK_END)
  188. additional = b'\0' * ((64 * 1024 * 1024)-fvfile.tell())
  189. fvfile.write(additional)
  190. # QEMU must be on that path
  191. # Unique Command and Args parameters per ARCH
  192. if (self.env.GetValue("TARGET_ARCH").upper() == "AARCH64"):
  193. cmd = "qemu-system-aarch64"
  194. args = "-M virt"
  195. args += " -cpu cortex-a57" # emulate cpu
  196. elif(self.env.GetValue("TARGET_ARCH").upper() == "ARM"):
  197. cmd = "qemu-system-arm"
  198. args = "-M virt"
  199. args += " -cpu cortex-a15" # emulate cpu
  200. else:
  201. raise NotImplementedError()
  202. # Common Args
  203. args += " -pflash " + Built_FV # path to fw
  204. args += " -m 1024" # 1gb memory
  205. # turn off network
  206. args += " -net none"
  207. # Serial messages out
  208. args += " -serial stdio"
  209. # Mount disk with startup.nsh
  210. args += f" -drive file=fat:rw:{VirtualDrive},format=raw,media=disk"
  211. # Conditional Args
  212. if (self.env.GetValue("QEMU_HEADLESS").upper() == "TRUE"):
  213. args += " -display none" # no graphics
  214. if (self.env.GetValue("MAKE_STARTUP_NSH").upper() == "TRUE"):
  215. f = open(os.path.join(VirtualDrive, "startup.nsh"), "w")
  216. f.write("BOOT SUCCESS !!! \n")
  217. # add commands here
  218. f.write("reset -s\n")
  219. f.close()
  220. ret = RunCmd(cmd, args)
  221. if ret == 0xc0000005:
  222. # for some reason getting a c0000005 on successful return
  223. return 0
  224. return ret