PlatformBuildLib.py 11 KB

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