BaseTools: Skip module AutoGen by comparing timestamp.

[Introduction]

The BaseTool Build.py AutoGen parse INF meta-file and generate
AutoGen.c/AutoGen.h/makefile. When we only change .c .h code, the
AutoGen might be not necessary, but Build.py spend a lot of time on it.
There's a -u flag to skip all module's AutoGen. In my environment, it save
35%~50% of time in rebuild a ROM.
However, if user change one .INF meta-file, then -u flag is not available.

[Idea]

AutoGen can compare meta-file's timestamp and decide if the module's
AutoGen can be skipped. With this, when a module's INF is changed, we
only run this module's AutoGen, we don't need to run other module's.

[Implementation]

In the end of a module's AutoGen, we create a AutoGenTimeStamp.
The file save a file list that related to this module's AutoGen.
In other word, the file list in AutoGenTimeStamp is INPUT files of
module AutoGen, AutoGenTimeStamp file is OUTPUT.
During rebuild, we compare time stamp between INPUT and OUTPUT, and
decide if we can skip it.

Below is the Input/Output of a module's AutoGen.

[Input]
  1. All the DSC/DEC/FDF used by the platform.
  2. Macro and PCD defined by Build Options such as "build -D AAA=TRUE
     --pcd BbbPcd=0".
  3. INF file of a module.
  4. Source files of a module, list in [Sources] section of INF.
  5. All the library link by the module.
  6. All the .h files included by the module's sources.

[Output]
  AutoGen.c/AutoGen.h/makefile/AutoGenTimeStamp

[Testing]

This patch save my build time. When I make a change without touching
DSC/DEC/FDF, it is absolutely much faster than original rebuild,
35%~50% time saving in my environment
(compare to original tool rebuild time).
If I change any DSC/DEC/FDF, there's no performance improve, because it
can't skip any module's AutoGen.

Please note that if your environment will generate DSC/FDF during prebuild,
it will not skip any AutoGen because of DSC timestamp is changed. This will
require prebuild script not to update metafile when content is not changed.
This commit is contained in:
Derek Lin 2017-02-24 15:26:19 +08:00 committed by Yonghong Zhu
parent 647636e175
commit c17956e0ee
4 changed files with 147 additions and 0 deletions

View File

@ -42,6 +42,7 @@ from GenPcdDb import CreatePcdDatabaseCode
from Workspace.MetaFileCommentParser import UsageList
from Common.MultipleWorkspace import MultipleWorkspace as mws
import InfSectionParser
import datetime
## Regular expression for splitting Dependency Expression string into tokens
gDepexTokenPattern = re.compile("(\(|\)|\w+| \S+\.inf)")
@ -640,6 +641,41 @@ class WorkspaceAutoGen(AutoGen):
self._MakeFileDir = None
self._BuildCommand = None
#
# Create BuildOptions Macro & PCD metafile.
#
content = 'gCommandLineDefines: '
content += str(GlobalData.gCommandLineDefines)
content += os.linesep
content += 'BuildOptionPcd: '
content += str(GlobalData.BuildOptionPcd)
SaveFileOnChange(os.path.join(self.BuildDir, 'BuildOptions'), content, False)
#
# Get set of workspace metafiles
#
AllWorkSpaceMetaFiles = self._GetMetaFiles(Target, Toolchain, Arch)
#
# Retrieve latest modified time of all metafiles
#
SrcTimeStamp = 0
for f in AllWorkSpaceMetaFiles:
if os.stat(f)[8] > SrcTimeStamp:
SrcTimeStamp = os.stat(f)[8]
self._SrcTimeStamp = SrcTimeStamp
#
# Write metafile list to build directory
#
AutoGenFilePath = os.path.join(self.BuildDir, 'AutoGen')
if os.path.exists (AutoGenFilePath):
os.remove(AutoGenFilePath)
if not os.path.exists(self.BuildDir):
os.makedirs(self.BuildDir)
with open(os.path.join(self.BuildDir, 'AutoGen'), 'w+') as file:
for f in AllWorkSpaceMetaFiles:
print >> file, f
return True
def _BuildOptionPcdValueFormat(self, TokenSpaceGuidCName, TokenCName, PcdDatumType, Value):
@ -668,6 +704,45 @@ class WorkspaceAutoGen(AutoGen):
Value = '0'
return Value
def _GetMetaFiles(self, Target, Toolchain, Arch):
AllWorkSpaceMetaFiles = set()
#
# add fdf
#
if self.FdfFile:
AllWorkSpaceMetaFiles.add (self.FdfFile.Path)
if self.FdfFile:
FdfFiles = GlobalData.gFdfParser.GetAllIncludedFile()
for f in FdfFiles:
AllWorkSpaceMetaFiles.add (f.FileName)
#
# add dsc
#
AllWorkSpaceMetaFiles.add(self.MetaFile.Path)
#
# add BuildOption metafile
#
AllWorkSpaceMetaFiles.add(os.path.join(self.BuildDir, 'BuildOptions'))
for Arch in self.ArchList:
Platform = self.BuildDatabase[self.MetaFile, Arch, Target, Toolchain]
PGen = PlatformAutoGen(self, self.MetaFile, Target, Toolchain, Arch)
#
# add dec
#
for Package in PGen.PackageList:
AllWorkSpaceMetaFiles.add(Package.MetaFile.Path)
#
# add included dsc
#
for filePath in Platform._RawData.IncludedFiles:
AllWorkSpaceMetaFiles.add(filePath.Path)
return AllWorkSpaceMetaFiles
## _CheckDuplicateInFV() method
#
# Check whether there is duplicate modules/files exist in FV section.
@ -2532,6 +2607,10 @@ class PlatformAutoGen(AutoGen):
# to the [depex] section in module's inf file.
#
class ModuleAutoGen(AutoGen):
## Cache the timestamps of metafiles of every module in a class variable
#
TimeDict = {}
## The real constructor of ModuleAutoGen
#
# This method is not supposed to be called by users of ModuleAutoGen. It's
@ -2633,6 +2712,11 @@ class ModuleAutoGen(AutoGen):
self._FileTypes = None
self._BuildRules = None
self._TimeStampPath = None
self.AutoGenDepSet = set()
## The Modules referenced to this Library
# Only Library has this attribute
self._ReferenceModules = []
@ -3968,6 +4052,8 @@ class ModuleAutoGen(AutoGen):
if self.IsMakeFileCreated:
return
if self.CanSkip():
return
if not self.IsLibrary and CreateLibraryMakeFile:
for LibraryAutoGen in self.LibraryAutoGenList:
@ -3984,6 +4070,7 @@ class ModuleAutoGen(AutoGen):
EdkLogger.debug(EdkLogger.DEBUG_9, "Skipped the generation of makefile for module %s [%s]" %
(self.Name, self.Arch))
self.CreateTimeStamp(Makefile)
self.IsMakeFileCreated = True
def CopyBinaryFiles(self):
@ -3999,6 +4086,8 @@ class ModuleAutoGen(AutoGen):
def CreateCodeFile(self, CreateLibraryCodeFile=True):
if self.IsCodeFileCreated:
return
if self.CanSkip():
return
# Need to generate PcdDatabase even PcdDriver is binarymodule
if self.IsBinaryModule and self.PcdIsDriver != '':
@ -4078,6 +4167,53 @@ class ModuleAutoGen(AutoGen):
self._ApplyBuildRule(Lib.Target, TAB_UNKNOWN_FILE)
return self._LibraryAutoGenList
## Decide whether we can skip the ModuleAutoGen process
# If any source file is newer than the modeule than we cannot skip
#
def CanSkip(self):
if not os.path.exists(self.GetTimeStampPath()):
return False
#last creation time of the module
DstTimeStamp = os.stat(self.GetTimeStampPath())[8]
SrcTimeStamp = self.Workspace._SrcTimeStamp
if SrcTimeStamp > DstTimeStamp:
return False
with open(self.GetTimeStampPath(),'r') as f:
for source in f:
source = source.rstrip('\n')
if source not in ModuleAutoGen.TimeDict :
ModuleAutoGen.TimeDict[source] = os.stat(source)[8]
if ModuleAutoGen.TimeDict[source] > DstTimeStamp:
return False
return True
def GetTimeStampPath(self):
if self._TimeStampPath == None:
self._TimeStampPath = os.path.join(self.MakeFileDir, 'AutoGenTimeStamp')
return self._TimeStampPath
def CreateTimeStamp(self, Makefile):
FileSet = set()
FileSet.add (self.MetaFile.Path)
for SourceFile in self.Module.Sources:
FileSet.add (SourceFile.Path)
for Lib in self.DependentLibraryList:
FileSet.add (Lib.MetaFile.Path)
for f in self.AutoGenDepSet:
FileSet.add (f.Path)
if os.path.exists (self.GetTimeStampPath()):
os.remove (self.GetTimeStampPath())
with open(self.GetTimeStampPath(), 'w+') as file:
for f in FileSet:
print >> file, f
Module = property(_GetModule)
Name = property(_GetBaseName)
Guid = property(_GetGuid)

View File

@ -801,6 +801,9 @@ cleanlib:
if not self.FileDependency[File]:
self.FileDependency[File] = ['$(FORCE_REBUILD)']
continue
self._AutoGenObject.AutoGenDepSet |= set(self.FileDependency[File])
# skip non-C files
if File.Ext not in [".c", ".C"] or File.Name == "AutoGen.c":
continue

View File

@ -4797,6 +4797,10 @@ class FdfParser:
return False
def GetAllIncludedFile (self):
global AllIncludeFileList
return AllIncludeFileList
if __name__ == "__main__":
import sys
try:

View File

@ -859,6 +859,8 @@ class DscParser(MetaFileParser):
SymbolPattern = ValueExpression.SymbolPattern
IncludedFiles = set()
## Constructor of DscParser
#
# Initialize object of DscParser
@ -1501,6 +1503,8 @@ class DscParser(MetaFileParser):
Parser = DscParser(IncludedFile1, self._FileType, self._Arch, IncludedFileTable,
Owner=Owner, From=Owner)
self.IncludedFiles.add (IncludedFile1)
# Does not allow lower level included file to include upper level included file
if Parser._From != Owner and int(Owner) > int (Parser._From):
EdkLogger.error('parser', FILE_ALREADY_EXIST, File=self._FileWithError,