Reviewed-by: Liming Gao <[email protected]> >-----Original Message----- >From: Zhu, Yonghong >Sent: Monday, September 04, 2017 4:44 PM >To: [email protected] >Cc: Gao, Liming <[email protected]> >Subject: [Patch] BaseTools: add support for BIOS build with binary cache > >Add three new options: >--hash enables hash-based caching during build process. when --hash is >enabled, build tool will base on the module hash value to do the >incremental build, without --hash, build tool will base on the >timestamp to do the incremental build. --hash option use md5 method to >get every hash value, DSC/FDF, tools_def.txt, build_rule.txt and build >command are calculated as global hash value, Package DEC and its >include header files are calculated as package hash value, Module >source files and its INF file are calculated as module hash value. >Library hash value will combine the global hash value and its dependent >package hash value. Driver hash value will combine the global hash >value, its dependent package hash value and its linked library hash >value. >When --hash and --bindest are specified, build tool will copy the >generated binary files for each module into the directory specified by >bindest at the build phase. Bindest caches all generated binary files. >When --hash and --binsource are specified, build tool will try to get >the binary files from the binary source directory at the build phase. >If the cached binary has the same hash value, it will be directly used. >Otherwise, build tool will compile the source files and generate the >binary files. > >Cc: Liming Gao <[email protected]> >Contributed-under: TianoCore Contribution Agreement 1.1 >Signed-off-by: Yonghong Zhu <[email protected]> >--- > BaseTools/Source/Python/AutoGen/AutoGen.py | 161 >+++++++++++++++++++++++++-- > BaseTools/Source/Python/Common/GlobalData.py | 7 ++ > BaseTools/Source/Python/build/build.py | 30 +++++ > 3 files changed, 189 insertions(+), 9 deletions(-) > >diff --git a/BaseTools/Source/Python/AutoGen/AutoGen.py >b/BaseTools/Source/Python/AutoGen/AutoGen.py >index 70e2e62..246bfec 100644 >--- a/BaseTools/Source/Python/AutoGen/AutoGen.py >+++ b/BaseTools/Source/Python/AutoGen/AutoGen.py >@@ -41,10 +41,11 @@ import Common.VpdInfoFile as VpdInfoFile > from GenPcdDb import CreatePcdDatabaseCode > from Workspace.MetaFileCommentParser import UsageList > from Common.MultipleWorkspace import MultipleWorkspace as mws > import InfSectionParser > import datetime >+import hashlib > > ## Regular expression for splitting Dependency Expression string into tokens > gDepexTokenPattern = re.compile("(\(|\)|\w+| \S+\.inf)") > > # >@@ -263,10 +264,14 @@ class WorkspaceAutoGen(AutoGen): > self.FdfFile = FlashDefinitionFile > self.FdTargetList = Fds > self.FvTargetList = Fvs > self.CapTargetList = Caps > self.AutoGenObjectList = [] >+ self._BuildDir = None >+ self._FvDir = None >+ self._MakeFileDir = None >+ self._BuildCommand = None > > # there's many relative directory operations, so ... > os.chdir(self.WorkspaceDir) > > # >@@ -642,10 +647,18 @@ class WorkspaceAutoGen(AutoGen): > # > Pa.CollectPlatformDynamicPcds() > Pa.CollectFixedAtBuildPcds() > self.AutoGenObjectList.append(Pa) > >+ # >+ # Generate Package level hash value >+ # >+ GlobalData.gPackageHash[Arch] = {} >+ if GlobalData.gUseHashCache: >+ for Pkg in Pkgs: >+ self._GenPkgLevelHash(Pkg) >+ > # > # Check PCDs token value conflict in each DEC file. > # > self._CheckAllPcdsTokenValueConflict() > >@@ -655,15 +668,10 @@ class WorkspaceAutoGen(AutoGen): > self._CheckPcdDefineAndType() > > # if self.FdfFile: > # self._CheckDuplicateInFV(Fdf) > >- self._BuildDir = None >- self._FvDir = None >- self._MakeFileDir = None >- self._BuildCommand = None >- > # > # Create BuildOptions Macro & PCD metafile, also add the Active > Platform >and FDF file. > # > content = 'gCommandLineDefines: ' > content += str(GlobalData.gCommandLineDefines) >@@ -675,10 +683,14 @@ class WorkspaceAutoGen(AutoGen): > content += str(self.Platform) > content += os.linesep > if self.FdfFile: > content += 'Flash Image Definition: ' > content += str(self.FdfFile) >+ content += os.linesep >+ if GlobalData.gBinCacheDest: >+ content += 'Cache of .efi location: ' >+ content += str(GlobalData.gBinCacheDest) > SaveFileOnChange(os.path.join(self.BuildDir, 'BuildOptions'), content, >False) > > # > # Create PcdToken Number file for Dynamic/DynamicEx Pcd. > # >@@ -704,10 +716,22 @@ class WorkspaceAutoGen(AutoGen): > for f in AllWorkSpaceMetaFiles: > if os.stat(f)[8] > SrcTimeStamp: > SrcTimeStamp = os.stat(f)[8] > self._SrcTimeStamp = SrcTimeStamp > >+ if GlobalData.gUseHashCache: >+ m = hashlib.md5() >+ for files in AllWorkSpaceMetaFiles: >+ if files.endswith('.dec'): >+ continue >+ f = open(files, 'r') >+ Content = f.read() >+ f.close() >+ m.update(Content) >+ SaveFileOnChange(os.path.join(self.BuildDir, 'AutoGen.hash'), >m.hexdigest(), True) >+ GlobalData.gPlatformHash = m.hexdigest() >+ > # > # Write metafile list to build directory > # > AutoGenFilePath = os.path.join(self.BuildDir, 'AutoGen') > if os.path.exists (AutoGenFilePath): >@@ -717,10 +741,33 @@ class WorkspaceAutoGen(AutoGen): > with open(os.path.join(self.BuildDir, 'AutoGen'), 'w+') as file: > for f in AllWorkSpaceMetaFiles: > print >> file, f > return True > >+ def _GenPkgLevelHash(self, Pkg): >+ PkgDir = os.path.join(self.BuildDir, Pkg.Arch, Pkg.PackageName) >+ CreateDirectory(PkgDir) >+ HashFile = os.path.join(PkgDir, Pkg.PackageName + '.hash') >+ m = hashlib.md5() >+ # Get .dec file's hash value >+ f = open(Pkg.MetaFile.Path, 'r') >+ Content = f.read() >+ f.close() >+ m.update(Content) >+ # Get include files hash value >+ if Pkg.Includes: >+ for inc in Pkg.Includes: >+ for Root, Dirs, Files in os.walk(str(inc)): >+ for File in Files: >+ File_Path = os.path.join(Root, File) >+ f = open(File_Path, 'r') >+ Content = f.read() >+ f.close() >+ m.update(Content) >+ SaveFileOnChange(HashFile, m.hexdigest(), True) >+ if Pkg.PackageName not in GlobalData.gPackageHash[Pkg.Arch]: >+ GlobalData.gPackageHash[Pkg.Arch][Pkg.PackageName] = >m.hexdigest() > > def _GetMetaFiles(self, Target, Toolchain, Arch): > AllWorkSpaceMetaFiles = set() > # > # add fdf >@@ -954,11 +1001,12 @@ class WorkspaceAutoGen(AutoGen): > self._FvDir = path.join(self.BuildDir, 'FV') > return self._FvDir > > ## Return the directory to store all intermediate and final files built > def _GetBuildDir(self): >- return self.AutoGenObjectList[0].BuildDir >+ if self._BuildDir == None: >+ return self.AutoGenObjectList[0].BuildDir > > ## Return the build output directory platform specifies > def _GetOutputDir(self): > return self.Platform.OutputDirectory > >@@ -3898,37 +3946,45 @@ class ModuleAutoGen(AutoGen): > AsBuiltInfDict['module_uefi_specification_version'] += >[self.Specification['UEFI_SPECIFICATION_VERSION']] > if 'PI_SPECIFICATION_VERSION' in self.Specification: > AsBuiltInfDict['module_pi_specification_version'] += >[self.Specification['PI_SPECIFICATION_VERSION']] > > OutputDir = self.OutputDir.replace('\\', '/').strip('/') >- >+ self.OutputFile = [] > for Item in self.CodaTargetList: > File = Item.Target.Path.replace('\\', > '/').strip('/').replace(OutputDir, >'').strip('/') >+ if File not in self.OutputFile: >+ self.OutputFile.append(File) > if Item.Target.Ext.lower() == '.aml': > AsBuiltInfDict['binary_item'] += ['ASL|' + File] > elif Item.Target.Ext.lower() == '.acpi': > AsBuiltInfDict['binary_item'] += ['ACPI|' + File] > elif Item.Target.Ext.lower() == '.efi': > AsBuiltInfDict['binary_item'] += ['PE32|' + self.Name + > '.efi'] > else: > AsBuiltInfDict['binary_item'] += ['BIN|' + File] > if self.DepexGenerated: >+ if self.Name + '.depex' not in self.OutputFile: >+ self.OutputFile.append(self.Name + '.depex') > if self.ModuleType in ['PEIM']: > AsBuiltInfDict['binary_item'] += ['PEI_DEPEX|' + self.Name + > '.depex'] > if self.ModuleType in ['DXE_DRIVER', 'DXE_RUNTIME_DRIVER', >'DXE_SAL_DRIVER', 'UEFI_DRIVER']: > AsBuiltInfDict['binary_item'] += ['DXE_DEPEX|' + self.Name + > '.depex'] > if self.ModuleType in ['DXE_SMM_DRIVER']: > AsBuiltInfDict['binary_item'] += ['SMM_DEPEX|' + self.Name + >'.depex'] > > Bin = self._GenOffsetBin() > if Bin: > AsBuiltInfDict['binary_item'] += ['BIN|%s' % Bin] >+ if Bin not in self.OutputFile: >+ self.OutputFile.append(Bin) > > for Root, Dirs, Files in os.walk(OutputDir): > for File in Files: > if File.lower().endswith('.pdb'): > AsBuiltInfDict['binary_item'] += ['DISPOSABLE|' + File] >+ if File not in self.OutputFile: >+ self.OutputFile.append(File) > HeaderComments = self.Module.HeaderComments > StartPos = 0 > for Index in range(len(HeaderComments)): > if HeaderComments[Index].find('@BinaryHeader') != -1: > HeaderComments[Index] = >HeaderComments[Index].replace('@BinaryHeader', '@file') >@@ -4105,11 +4161,52 @@ class ModuleAutoGen(AutoGen): > AsBuiltInf.Append(gAsBuiltInfHeaderString.Replace(AsBuiltInfDict)) > > SaveFileOnChange(os.path.join(self.OutputDir, self.Name + '.inf'), >str(AsBuiltInf), False) > > self.IsAsBuiltInfCreated = True >- >+ if GlobalData.gBinCacheDest: >+ self.CopyModuleToCache() >+ >+ def CopyModuleToCache(self): >+ FileDir = path.join(GlobalData.gBinCacheDest, self.Arch, >self.SourceDir, >self.MetaFile.BaseName) >+ CreateDirectory (FileDir) >+ HashFile = path.join(self.BuildDir, self.Name + '.hash') >+ ModuleFile = path.join(self.OutputDir, self.Name + '.inf') >+ if os.path.exists(HashFile): >+ shutil.copy2(HashFile, FileDir) >+ if os.path.exists(ModuleFile): >+ shutil.copy2(ModuleFile, FileDir) >+ if self.OutputFile: >+ for File in self.OutputFile: >+ if not os.path.isabs(File): >+ File = os.path.join(self.OutputDir, File) >+ if os.path.exists(File): >+ shutil.copy2(File, FileDir) >+ >+ def AttemptModuleCacheCopy(self): >+ if self.IsBinaryModule: >+ return False >+ FileDir = path.join(GlobalData.gBinCacheSource, self.Arch, >self.SourceDir, >self.MetaFile.BaseName) >+ HashFile = path.join(FileDir, self.Name + '.hash') >+ if os.path.exists(HashFile): >+ f = open(HashFile, 'r') >+ CacheHash = f.read() >+ f.close() >+ if GlobalData.gModuleHash[self.Arch][self.Name]: >+ if CacheHash == GlobalData.gModuleHash[self.Arch][self.Name]: >+ for root, dir, files in os.walk(FileDir): >+ for f in files: >+ if self.Name + '.hash' in f: >+ shutil.copy2(HashFile, self.BuildDir) >+ else: >+ File = path.join(root, f) >+ shutil.copy2(File, self.OutputDir) >+ if self.Name == "PcdPeim" or self.Name == "PcdDxe": >+ CreatePcdDatabaseCode(self, TemplateString(), >TemplateString()) >+ return True >+ return False >+ > ## Create makefile for the module and its dependent libraries > # > # @param CreateLibraryMakeFile Flag indicating if or not the > makefiles >of > # dependent libraries will be > created > # >@@ -4233,12 +4330,58 @@ class ModuleAutoGen(AutoGen): > self._LibraryAutoGenList.append(La) > for Lib in La.CodaTargetList: > self._ApplyBuildRule(Lib.Target, TAB_UNKNOWN_FILE) > return self._LibraryAutoGenList > >+ def GenModuleHash(self): >+ if self.Arch not in GlobalData.gModuleHash: >+ GlobalData.gModuleHash[self.Arch] = {} >+ m = hashlib.md5() >+ # Add Platform level hash >+ m.update(GlobalData.gPlatformHash) >+ # Add Package level hash >+ if self.DependentPackageList: >+ for Pkg in self.DependentPackageList: >+ if Pkg.PackageName in GlobalData.gPackageHash[self.Arch]: >+ >m.update(GlobalData.gPackageHash[self.Arch][Pkg.PackageName]) >+ >+ # Add Library hash >+ if self.LibraryAutoGenList: >+ for Lib in self.LibraryAutoGenList: >+ if Lib.Name not in GlobalData.gModuleHash[self.Arch]: >+ Lib.GenModuleHash() >+ m.update(GlobalData.gModuleHash[self.Arch][Lib.Name]) >+ >+ # Add Module self >+ f = open(str(self.MetaFile), 'r') >+ Content = f.read() >+ f.close() >+ m.update(Content) >+ # Add Module's source files >+ if self.SourceFileList: >+ for File in self.SourceFileList: >+ f = open(str(File), 'r') >+ Content = f.read() >+ f.close() >+ m.update(Content) >+ >+ ModuleHashFile = path.join(self.BuildDir, self.Name + ".hash") >+ if self.Name not in GlobalData.gModuleHash[self.Arch]: >+ GlobalData.gModuleHash[self.Arch][self.Name] = m.hexdigest() >+ if GlobalData.gBinCacheSource: >+ CacheValid = self.AttemptModuleCacheCopy() >+ if CacheValid: >+ return False >+ return SaveFileOnChange(ModuleHashFile, m.hexdigest(), True) >+ >+ ## Decide whether we can skip the ModuleAutoGen process >+ def CanSkipbyHash(self): >+ if GlobalData.gUseHashCache: >+ return not self.GenModuleHash() >+ > ## Decide whether we can skip the ModuleAutoGen process >- # If any source file is newer than the modeule than we cannot skip >+ # If any source file is newer than the module than we cannot skip > # > def CanSkip(self): > if not os.path.exists(self.GetTimeStampPath()): > return False > #last creation time of the module >diff --git a/BaseTools/Source/Python/Common/GlobalData.py >b/BaseTools/Source/Python/Common/GlobalData.py >index 667877e..45e7ea0 100644 >--- a/BaseTools/Source/Python/Common/GlobalData.py >+++ b/BaseTools/Source/Python/Common/GlobalData.py >@@ -85,5 +85,12 @@ BuildOptionPcd = [] > # > MixedPcd = {} > > # Pcd name for the Pcd which used in the Conditional directives > gConditionalPcds = [] >+ >+gUseHashCache = None >+gBinCacheDest = None >+gBinCacheSource = None >+gPlatformHash = None >+gPackageHash = {} >+gModuleHash = {} >diff --git a/BaseTools/Source/Python/build/build.py >b/BaseTools/Source/Python/build/build.py >index 7436453..bb34b87 100644 >--- a/BaseTools/Source/Python/build/build.py >+++ b/BaseTools/Source/Python/build/build.py >@@ -764,10 +764,34 @@ class Build(): > self.TargetTxt = TargetTxtClassObject() > self.ToolDef = ToolDefClassObject() > GlobalData.BuildOptionPcd = BuildOptions.OptionPcd > #Set global flag for build mode > GlobalData.gIgnoreSource = BuildOptions.IgnoreSources >+ GlobalData.gUseHashCache = BuildOptions.UseHashCache >+ GlobalData.gBinCacheDest = BuildOptions.BinCacheDest >+ GlobalData.gBinCacheSource = BuildOptions.BinCacheSource >+ >+ if GlobalData.gBinCacheDest and not GlobalData.gUseHashCache: >+ EdkLogger.error("build", OPTION_NOT_SUPPORTED, ExtraData="-- >bindest must be used together with --hashcache.") >+ >+ if GlobalData.gBinCacheSource and not GlobalData.gUseHashCache: >+ EdkLogger.error("build", OPTION_NOT_SUPPORTED, ExtraData="-- >binsource must be used together with --hashcache.") >+ >+ if GlobalData.gBinCacheDest and GlobalData.gBinCacheSource: >+ EdkLogger.error("build", OPTION_NOT_SUPPORTED, ExtraData="-- >bindest can not be used together with --binsource.") >+ >+ if GlobalData.gBinCacheSource: >+ BinCacheSource = os.path.normpath(GlobalData.gBinCacheSource) >+ if not os.path.isabs(BinCacheSource): >+ BinCacheSource = mws.join(self.WorkspaceDir, BinCacheSource) >+ GlobalData.gBinCacheSource = BinCacheSource >+ >+ if GlobalData.gBinCacheDest: >+ BinCacheDest = os.path.normpath(GlobalData.gBinCacheDest) >+ if not os.path.isabs(BinCacheDest): >+ BinCacheDest = mws.join(self.WorkspaceDir, BinCacheDest) >+ GlobalData.gBinCacheDest = BinCacheDest > > if self.ConfDirectory: > # Get alternate Conf location, if it is absolute, then just use > the absolute >directory name > ConfDirectoryPath = os.path.normpath(self.ConfDirectory) > >@@ -1908,10 +1932,13 @@ class Build(): > # Get ModuleAutoGen object to generate C code file and >makefile > Ma = ModuleAutoGen(Wa, Module, BuildTarget, > ToolChain, Arch, >self.PlatformFile) > > if Ma == None: > continue >+ if Ma.CanSkipbyHash(): >+ continue >+ > # Not to auto-gen for targets 'clean', 'cleanlib', > 'cleanall', 'run', 'fds' > if self.Target not in ['clean', 'cleanlib', > 'cleanall', 'run', 'fds']: > # for target which must generate AutoGen code and > makefile > if not self.SkipAutoGen or self.Target == 'genc': > Ma.CreateCodeFile(True) >@@ -2213,10 +2240,13 @@ def MyOptionParser(): > Parser.add_option("--conf", action="store", type="string", >dest="ConfDirectory", help="Specify the customized Conf directory.") > Parser.add_option("--check-usage", action="store_true", >dest="CheckUsage", default=False, help="Check usage content of entries >listed in INF file.") > Parser.add_option("--ignore-sources", action="store_true", >dest="IgnoreSources", default=False, help="Focus to a binary build and ignore >all source files") > Parser.add_option("--pcd", action="append", dest="OptionPcd", help="Set >PCD value by command line. Format: \"PcdName=Value\" ") > Parser.add_option("-l", "--cmd-len", action="store", type="int", >dest="CommandLength", help="Specify the maximum line length of build >command. Default is 4096.") >+ Parser.add_option("--hash", action="store_true", dest="UseHashCache", >default=False, help="Enable hash-based caching during build process.") >+ Parser.add_option("--bindest", action="store", type="string", >dest="BinCacheDest", help="Generate a cache of binary files in the specified >directory.") >+ Parser.add_option("--binsource", action="store", type="string", >dest="BinCacheSource", help="Consume a cache of binary files from the >specified directory.") > > (Opt, Args) = Parser.parse_args() > return (Opt, Args) > > ## Tool entrance method >-- >2.6.1.windows.1
_______________________________________________ edk2-devel mailing list [email protected] https://lists.01.org/mailman/listinfo/edk2-devel

