Changed unknown error message
[people/mcb30/basetools.git] / Source / Python / build / build.py
1 ## @file
2 # build a platform or a module
3 #
4 #  Copyright (c) 2007, Intel Corporation
5 #
6 #  All rights reserved. This program and the accompanying materials
7 #  are licensed and made available under the terms and conditions of the BSD License
8 #  which accompanies this distribution.  The full text of the license may be found at
9 #  http://opensource.org/licenses/bsd-license.php
10 #
11 #  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12 #  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13 #
14
15 ##
16 # Import Modules
17 #
18 import os
19 import re
20 import sys
21 import glob
22 import time
23 import traceback
24
25 from threading import *
26 from optparse import OptionParser
27 from subprocess import *
28 from Common import Misc as Utils
29
30 from Common.TargetTxtClassObject import *
31 from Common.ToolDefClassObject import *
32 from Common.DataType import *
33 from AutoGen.AutoGen import *
34 from Common.BuildToolError import *
35 from Workspace.WorkspaceDatabase import *
36
37 import Common.EdkLogger
38 import Common.GlobalData as GlobalData
39
40 # Version and Copyright
41 VersionNumber = "0.1"
42 __version__ = "%prog Version " + VersionNumber
43 __copyright__ = "Copyright (c) 2007, Intel Corporation  All rights reserved."
44
45 ## standard targets of build command
46 gSupportedTarget = ['all', 'genc', 'genmake', 'modules', 'libraries', 'fds', 'clean', 'cleanall', 'cleanlib', 'run']
47
48 ## build configuration file
49 gBuildConfiguration = "Conf/target.txt"
50 gBuildCacheDir = "Conf/.cache"
51 gToolsDefinition = "Conf/tools_def.txt"
52
53 ## Check environment PATH variable to make sure the specified tool is found
54 #
55 #   If the tool is found in the PATH, then True is returned
56 #   Otherwise, False is returned
57 #
58 def IsToolInPath(tool):
59     if os.environ.has_key('PATHEXT'):
60         extns = os.environ['PATHEXT'].split(os.path.pathsep)
61     else:
62         extns = ('',)
63     for pathDir in os.environ['PATH'].split(os.path.pathsep):
64         for ext in extns:
65             if os.path.exists(os.path.join(pathDir, tool + ext)):
66                 return True
67     return False
68
69 ## Check environment variables
70 #
71 #  Check environment variables that must be set for build. Currently they are
72 #
73 #   WORKSPACE           The directory all packages/platforms start from
74 #   EDK_TOOLS_PATH      The directory contains all tools needed by the build
75 #   PATH                $(EDK_TOOLS_PATH)/Bin/<sys> must be set in PATH
76 #
77 #   If any of above environment variable is not set or has error, the build
78 #   will be broken.
79 #
80 def CheckEnvVariable():
81     # check WORKSPACE
82     if "WORKSPACE" not in os.environ:
83         EdkLogger.error("build", ATTRIBUTE_NOT_AVAILABLE, "Environment variable not found",
84                         ExtraData="WORKSPACE")
85
86     WorkspaceDir = os.path.normpath(os.environ["WORKSPACE"])
87     if not os.path.exists(WorkspaceDir):
88         EdkLogger.error("build", FILE_NOT_FOUND, ExtraData="WORKSPACE = %s" % WorkspaceDir)
89     os.environ["WORKSPACE"] = WorkspaceDir
90
91     #
92     # Check EFI_SOURCE (R8 build convention). EDK_SOURCE will always point to ECP
93     #
94     os.environ["EDK_SOURCE"] = os.path.join(WorkspaceDir, GlobalData.gEdkCompatibilityPkg)
95     if "EFI_SOURCE" not in os.environ:
96         os.environ["EFI_SOURCE"] = os.environ["EDK_SOURCE"]
97
98     EfiSourceDir = os.path.normpath(os.environ["EFI_SOURCE"])
99     EdkSourceDir = os.path.normpath(os.environ["EDK_SOURCE"])
100     if not os.path.exists(EdkSourceDir):
101         EdkLogger.verbose("EDK_SOURCE = %s doesn't exist. R8 modules could not be built." % EdkSourceDir)
102     if not os.path.exists(EfiSourceDir):
103         EdkLogger.verbose("EFI_SOURCE = %s doesn't exist. R8 modules could not be built." % EfiSourceDir)
104
105     # change absolute path to relative path to WORKSPACE
106     if EfiSourceDir.upper().find(WorkspaceDir.upper()) != 0:
107         EdkLogger.error("build", PARAMETER_INVALID, "EFI_SOURCE is not under WORKSPACE",
108                         ExtraData="WORKSPACE = %s\n    EFI_SOURCE = %s" % (WorkspaceDir, EfiSourceDir))
109     if EdkSourceDir.upper().find(WorkspaceDir.upper()) != 0:
110         EdkLogger.error("build", PARAMETER_INVALID, "EDK_SOURCE is not under WORKSPACE",
111                         ExtraData="WORKSPACE = %s\n    EDK_SOURCE = %s" % (WorkspaceDir, EdkSourceDir))
112     EfiSourceDir = EfiSourceDir[len(WorkspaceDir)+1:]
113     EdkSourceDir = EdkSourceDir[len(WorkspaceDir)+1:]
114
115     # check EDK_TOOLS_PATH
116     if "EDK_TOOLS_PATH" not in os.environ:
117         EdkLogger.error("build", ATTRIBUTE_NOT_AVAILABLE, "Environment variable not found",
118                         ExtraData="EDK_TOOLS_PATH")
119
120     # check PATH
121     if "PATH" not in os.environ:
122         EdkLogger.error("build", ATTRIBUTE_NOT_AVAILABLE, "Environment variable not found",
123                         ExtraData="PATH")
124
125     PathString = os.environ["PATH"]
126     ToolPath = os.path.normpath(os.path.join(os.environ["EDK_TOOLS_PATH"], 'Bin', sys.platform.title()))
127
128     if not IsToolInPath('build'):
129         os.environ['PATH'] = os.path.pathsep.join((os.environ['PATH'], ToolPath))
130
131         EdkLogger.error("build", ATTRIBUTE_NOT_AVAILABLE, "Please execute %s to set %s in environment variable: PATH!\n"
132                             % (os.path.normpath(os.path.join(PathString, 'edksetup.bat')), ToolPath))
133
134     # for macro replacement in R9 DSC/DEC/INF file
135     GlobalData.gGlobalDefines["WORKSPACE"] = ""
136
137     # for macro replacement in R8 INF file
138     GlobalData.gGlobalDefines["EFI_SOURCE"] = EfiSourceDir
139     GlobalData.gGlobalDefines["EDK_SOURCE"] = EdkSourceDir
140
141 ## Get normalized file path
142 #
143 # Convert the path to be local format, and remove the WORKSPACE path at the
144 # beginning if the file path is given in full path.
145 #
146 # @param  FilePath      File path to be normalized
147 # @param  Workspace     Workspace path which the FilePath will be checked against
148 #
149 # @retval string        The normalized file path
150 #
151 def NormFile(FilePath, Workspace):
152     # check if the path is absolute or relative
153     if os.path.isabs(FilePath):
154         FileFullPath = os.path.normpath(FilePath)
155     else:
156         FileFullPath = os.path.normpath(os.path.join(Workspace, FilePath))
157
158     # check if the file path exists or not
159     if not os.path.isfile(FileFullPath):
160         EdkLogger.error("build", FILE_NOT_FOUND, ExtraData="\t%s (Please give file in absolute path or relative to WORKSPACE)"  % FileFullPath)
161
162     # remove workspace directory from the beginning part of the file path
163     if Workspace[-1] in ["\\", "/"]:
164         return FileFullPath[len(Workspace):]
165     else:
166         return FileFullPath[(len(Workspace) + 1):]
167
168 ## Get the output of an external program
169 #
170 # This is the entrance method of thread reading output of an external program and
171 # putting them in STDOUT/STDERR of current program.
172 #
173 # @param  From      The stream message read from
174 # @param  To        The stream message put on
175 # @param  ExitFlag  The flag used to indicate stopping reading
176 #
177 def ReadMessage(From, To, ExitFlag):
178     while True:
179         # read one line a time
180         Line = From.readline()
181         # empty string means "end"
182         if Line != None and Line != "":
183             To(Line.rstrip())
184         else:
185             break
186         if ExitFlag.isSet():
187             break
188
189 ## Launch an external program
190 #
191 # This method will call subprocess.Popen to execute an external program with
192 # given options in specified directory. Because of the dead-lock issue during
193 # redirecting output of the external program, threads are used to to do the
194 # redirection work.
195 #
196 # @param  Command               A list or string containing the call of the program
197 # @param  WorkingDir            The directory in which the program will be running
198 #
199 def LaunchCommand(Command, WorkingDir):
200     # if working directory doesn't exist, Popen() will raise an exception
201     if not os.path.isdir(WorkingDir):
202         EdkLogger.error("build", FILE_NOT_FOUND, ExtraData=WorkingDir)
203
204     Proc = None
205     EndOfProcedure = None
206     try:
207         # launch the command
208         Proc = Popen(Command, stdout=PIPE, stderr=PIPE, env=os.environ, cwd=WorkingDir)
209
210         # launch two threads to read the STDOUT and STDERR
211         EndOfProcedure = Event()
212         EndOfProcedure.clear()
213         if Proc.stdout:
214             StdOutThread = Thread(target=ReadMessage, args=(Proc.stdout, EdkLogger.info, EndOfProcedure))
215             StdOutThread.setName("STDOUT-Redirector")
216             StdOutThread.setDaemon(False)
217             StdOutThread.start()
218
219         if Proc.stderr:
220             StdErrThread = Thread(target=ReadMessage, args=(Proc.stderr, EdkLogger.quiet, EndOfProcedure))
221             StdErrThread.setName("STDERR-Redirector")
222             StdErrThread.setDaemon(False)
223             StdErrThread.start()
224
225         # waiting for program exit
226         Proc.wait()
227     except: # in case of aborting
228         # terminate the threads redirecting the program output
229         if EndOfProcedure != None:
230             EndOfProcedure.set()
231         if Proc == None:
232             if type(Command) != type(""):
233                 Command = " ".join(Command)
234             EdkLogger.error("build", COMMAND_FAILURE, "Failed to start command", ExtraData="%s [%s]" % (Command, WorkingDir))
235
236     if Proc.stdout:
237         StdOutThread.join()
238     if Proc.stderr:
239         StdErrThread.join()
240
241     # check the return code of the program
242     if Proc.returncode != 0:
243         if type(Command) != type(""):
244             Command = " ".join(Command)
245         EdkLogger.error("build", COMMAND_FAILURE, ExtraData="%s [%s]" % (Command, WorkingDir))
246
247 ## The smallest unit that can be built in multi-thread build mode
248 #
249 # This is the base class of build unit. The "Obj" parameter must provide
250 # __str__(), __eq__() and __hash__() methods. Otherwise there could be build units
251 # missing build.
252 #
253 # Currently the "Obj" should be only ModuleAutoGen or PlatformAutoGen objects.
254 #
255 class BuildUnit:
256     ## The constructor
257     #
258     #   @param  self        The object pointer
259     #   @param  Obj         The object the build is working on
260     #   @param  Target      The build target name, one of gSupportedTarget
261     #   @param  Dependency  The BuildUnit(s) which must be completed in advance
262     #   @param  WorkingDir  The directory build command starts in
263     #
264     def __init__(self, Obj, BuildCommand, Target, Dependency, WorkingDir="."):
265         self.BuildObject = Obj
266         self.Dependency = Dependency
267         self.WorkingDir = WorkingDir
268         self.Target = Target
269         self.BuildCommand = BuildCommand
270         if BuildCommand == None or len(BuildCommand) == 0:
271             EdkLogger.error("build", OPTION_MISSING, "No build command found for",
272                             ExtraData=str(Obj))
273
274     ## str() method
275     #
276     #   It just returns the string representaion of self.BuildObject
277     #
278     #   @param  self        The object pointer
279     #
280     def __str__(self):
281         return str(self.BuildObject)
282
283     ## "==" operator method
284     #
285     #   It just compares self.BuildObject with "Other". So self.BuildObject must
286     #   provide its own __eq__() method.
287     #
288     #   @param  self        The object pointer
289     #   @param  Other       The other BuildUnit object compared to
290     #
291     def __eq__(self, Other):
292         return Other != None and self.BuildObject == Other.BuildObject \
293                 and self.BuildObject.Arch == Other.BuildObject.Arch
294
295     ## hash() method
296     #
297     #   It just returns the hash value of self.BuildObject which must be hashable.
298     #
299     #   @param  self        The object pointer
300     #
301     def __hash__(self):
302         return hash(self.BuildObject) + hash(self.BuildObject.Arch)
303
304 ## The smallest module unit that can be built by nmake/make command in multi-thread build mode
305 #
306 # This class is for module build by nmake/make build system. The "Obj" parameter
307 # must provide __str__(), __eq__() and __hash__() methods. Otherwise there could
308 # be make units missing build.
309 #
310 # Currently the "Obj" should be only ModuleAutoGen object.
311 #
312 class ModuleMakeUnit(BuildUnit):
313     ## The constructor
314     #
315     #   @param  self        The object pointer
316     #   @param  Obj         The ModuleAutoGen object the build is working on
317     #   @param  Target      The build target name, one of gSupportedTarget
318     #
319     def __init__(self, Obj, Target):
320         Dependency = [ModuleMakeUnit(La, Target) for La in Obj.LibraryAutoGenList]
321         BuildUnit.__init__(self, Obj, Obj.BuildCommand, Target, Dependency, Obj.MakeFileDir)
322         if Target in [None, "", "all"]:
323             self.Target = "pbuild"
324
325 ## The smallest platform unit that can be built by nmake/make command in multi-thread build mode
326 #
327 # This class is for platform build by nmake/make build system. The "Obj" parameter
328 # must provide __str__(), __eq__() and __hash__() methods. Otherwise there could
329 # be make units missing build.
330 #
331 # Currently the "Obj" should be only PlatformAutoGen object.
332 #
333 class PlatformMakeUnit(BuildUnit):
334     ## The constructor
335     #
336     #   @param  self        The object pointer
337     #   @param  Obj         The PlatformAutoGen object the build is working on
338     #   @param  Target      The build target name, one of gSupportedTarget
339     #
340     def __init__(self, Obj, Target):
341         Dependency = [ModuleMakeUnit(Lib, Target) for Lib in self.BuildObject.LibraryAutoGenList]
342         Dependency.extend([ModuleMakeUnit(Mod, Target) for Mod in self.BuildObject.ModuleAutoGenList])
343         BuildUnit.__init__(self, Obj, Obj.BuildCommand, Target, Dependency, Obj.MakeFileDir)
344
345 ## The class representing the task of a module build or platform build
346 #
347 # This class manages the build tasks in multi-thread build mode. Its jobs include
348 # scheduling thread running, catching thread error, monitor the thread status, etc.
349 #
350 class BuildTask:
351     # queue for tasks waiting for schedule
352     _PendingQueue = sdict()
353     _PendingQueueLock = threading.Lock()
354
355     # queue for tasks ready for running
356     _ReadyQueue = sdict()
357     _ReadyQueueLock = threading.Lock()
358
359     # queue for run tasks
360     _RunningQueue = sdict()
361     _RunningQueueLock = threading.Lock()
362
363     # queue containing all build tasks, in case duplicate build
364     _TaskQueue = sdict()
365
366     # flag indicating error occurs in a running thread
367     _ErrorFlag = threading.Event()
368     _ErrorFlag.clear()
369     _ErrorMessage = ""
370
371     # BoundedSemaphore object used to control the number of running threads
372     _Thread = None
373
374     # flag indicating if the scheduler is started or not
375     _SchedulerStopped = threading.Event()
376     _SchedulerStopped.set()
377
378     ## Start the task scheduler thread
379     #
380     #   @param  MaxThreadNumber     The maximum thread number
381     #   @param  ExitFlag            Flag used to end the scheduler
382     #
383     @staticmethod
384     def StartScheduler(MaxThreadNumber, ExitFlag):
385         SchedulerThread = Thread(target=BuildTask.Scheduler, args=(MaxThreadNumber, ExitFlag))
386         SchedulerThread.setName("Build-Task-Scheduler")
387         SchedulerThread.setDaemon(False)
388         SchedulerThread.start()
389         # wait for the scheduler to be started, especially useful in Linux
390         while not BuildTask.IsOnGoing():
391             time.sleep(0.01)
392
393     ## Scheduler method
394     #
395     #   @param  MaxThreadNumber     The maximum thread number
396     #   @param  ExitFlag            Flag used to end the scheduler
397     #
398     @staticmethod
399     def Scheduler(MaxThreadNumber, ExitFlag):
400         BuildTask._SchedulerStopped.clear()
401         try:
402             # use BoundedSemaphore to control the maximum running threads
403             BuildTask._Thread = BoundedSemaphore(MaxThreadNumber)
404             #
405             # scheduling loop, which will exits when no pending/ready task and
406             # indicated to do so, or there's error in running thread
407             #
408             while (len(BuildTask._PendingQueue) > 0 or len(BuildTask._ReadyQueue) > 0 \
409                    or not ExitFlag.isSet()) and not BuildTask._ErrorFlag.isSet():
410                 EdkLogger.debug(EdkLogger.DEBUG_8, "Pending Queue (%d), Ready Queue (%d)"
411                                 % (len(BuildTask._PendingQueue), len(BuildTask._ReadyQueue)))
412                 # get all pending tasks
413                 BuildTask._PendingQueueLock.acquire()
414                 BuildObjectList = BuildTask._PendingQueue.keys()
415                 BuildTask._PendingQueueLock.release()
416
417                 #
418                 # check if their dependency is resolved, and if true, move them
419                 # into ready queue
420                 #
421                 for BuildObject in BuildObjectList:
422                     Bt = BuildTask._PendingQueue[BuildObject]
423                     if Bt.IsReady():
424                         BuildTask._PendingQueueLock.acquire()
425                         BuildTask._ReadyQueue[BuildObject] = BuildTask._PendingQueue.pop(BuildObject)
426                         BuildTask._PendingQueueLock.release()
427
428                 # launch build thread until the maximum number of threads is reached
429                 while not BuildTask._ErrorFlag.isSet():
430                     # empty ready queue, do nothing further
431                     if len(BuildTask._ReadyQueue) == 0:
432                         break
433
434                     # wait for active thread(s) exit
435                     BuildTask._Thread.acquire(True)
436
437                     # start a new build thread
438                     Bo = BuildTask._ReadyQueue.keys()[0]
439                     Bt = BuildTask._ReadyQueue.pop(Bo)
440
441                     # move into running queue
442                     BuildTask._RunningQueueLock.acquire()
443                     BuildTask._RunningQueue[Bo] = Bt
444                     BuildTask._RunningQueueLock.release()
445
446                     Bt.Start()
447
448                 # avoid tense loop
449                 time.sleep(0.01)
450
451             # wait for all running threads exit
452             if BuildTask._ErrorFlag.isSet():
453                 EdkLogger.quiet("\nWaiting for all build threads exit...")
454             # while not BuildTask._ErrorFlag.isSet() and \
455             while len(BuildTask._RunningQueue) > 0:
456                 EdkLogger.verbose("Waiting for thread ending...(%d)" % len(BuildTask._RunningQueue))
457                 EdkLogger.debug(EdkLogger.DEBUG_8, "Threads [%s]" % ", ".join([Th.getName() for Th in threading.enumerate()]))
458                 # avoid tense loop
459                 time.sleep(0.1)
460         except BaseException, X:
461             #
462             # TRICK: hide the output of threads left runing, so that the user can
463             #        catch the error message easily
464             #
465             EdkLogger.SetLevel(EdkLogger.QUIET)
466             BuildTask._ErrorFlag.set()
467             BuildTask._ErrorMessage = "build thread scheduler error\n\t%s" % str(X)
468
469         BuildTask._PendingQueue.clear()
470         BuildTask._ReadyQueue.clear()
471         BuildTask._RunningQueue.clear()
472         BuildTask._TaskQueue.clear()
473         BuildTask._SchedulerStopped.set()
474
475     ## Wait for all running method exit
476     #
477     @staticmethod
478     def WaitForComplete():
479         BuildTask._SchedulerStopped.wait()
480
481     ## Check if the scheduler is running or not
482     #
483     @staticmethod
484     def IsOnGoing():
485         return not BuildTask._SchedulerStopped.isSet()
486
487     ## Abort the build
488     @staticmethod
489     def Abort():
490         if BuildTask.IsOnGoing():
491             BuildTask._ErrorFlag.set()
492             BuildTask.WaitForComplete()
493
494     ## Check if there's error in running thread
495     #
496     #   Since the main thread cannot catch exceptions in other thread, we have to
497     #   use threading.Event to communicate this formation to main thread.
498     #
499     @staticmethod
500     def HasError():
501         return BuildTask._ErrorFlag.isSet()
502
503     ## Get error message in running thread
504     #
505     #   Since the main thread cannot catch exceptions in other thread, we have to
506     #   use a static variable to communicate this message to main thread.
507     #
508     @staticmethod
509     def GetErrorMessage():
510         return BuildTask._ErrorMessage
511
512     ## Factory method to create a BuildTask object
513     #
514     #   This method will check if a module is building or has been built. And if
515     #   true, just return the associated BuildTask object in the _TaskQueue. If
516     #   not, create and return a new BuildTask object. The new BuildTask object
517     #   will be appended to the _PendingQueue for scheduling later.
518     #
519     #   @param  BuildItem       A BuildUnit object representing a build object
520     #   @param  Dependency      The dependent build object of BuildItem
521     #
522     @staticmethod
523     def New(BuildItem, Dependency=None):
524         if BuildItem in BuildTask._TaskQueue:
525             Bt = BuildTask._TaskQueue[BuildItem]
526             return Bt
527
528         Bt = BuildTask()
529         Bt._Init(BuildItem, Dependency)
530         BuildTask._TaskQueue[BuildItem] = Bt
531
532         BuildTask._PendingQueueLock.acquire()
533         BuildTask._PendingQueue[BuildItem] = Bt
534         BuildTask._PendingQueueLock.release()
535
536         return Bt
537
538     ## The real constructor of BuildTask
539     #
540     #   @param  BuildItem       A BuildUnit object representing a build object
541     #   @param  Dependency      The dependent build object of BuildItem
542     #
543     def _Init(self, BuildItem, Dependency=None):
544         self.BuildItem = BuildItem
545
546         self.DependencyList = []
547         if Dependency == None:
548             Dependency = BuildItem.Dependency
549         else:
550             Dependency.extend(BuildItem.Dependency)
551         self.AddDependency(Dependency)
552         # flag indicating build completes, used to avoid unnecessary re-build
553         self.CompleteFlag = False
554
555     ## Check if all dependent build tasks are completed or not
556     #
557     def IsReady(self):
558         ReadyFlag = True
559         for Dep in self.DependencyList:
560             if Dep.CompleteFlag == True:
561                 continue
562             ReadyFlag = False
563             break
564
565         return ReadyFlag
566
567     ## Add dependent build task
568     #
569     #   @param  Dependency      The list of dependent build objects
570     #
571     def AddDependency(self, Dependency):
572         for Dep in Dependency:
573             self.DependencyList.append(BuildTask.New(Dep))    # BuildTask list
574
575     ## The thread wrapper of LaunchCommand function
576     #
577     # @param  Command               A list or string contains the call of the command
578     # @param  WorkingDir            The directory in which the program will be running
579     #
580     def _CommandThread(self, Command, WorkingDir):
581         try:
582             LaunchCommand(Command, WorkingDir)
583             self.CompleteFlag = True
584         except:
585             #
586             # TRICK: hide the output of threads left runing, so that the user can
587             #        catch the error message easily
588             #
589             EdkLogger.SetLevel(EdkLogger.QUIET)
590             BuildTask._ErrorFlag.set()
591             BuildTask._ErrorMessage = "%s broken\n    %s [%s]" % \
592                                       (threading.currentThread().getName(), Command, WorkingDir)
593         # indicate there's a thread is available for another build task
594         BuildTask._RunningQueueLock.acquire()
595         BuildTask._RunningQueue.pop(self.BuildItem)
596         BuildTask._RunningQueueLock.release()
597         BuildTask._Thread.release()
598
599     ## Start build task thread
600     #
601     def Start(self):
602         Command = self.BuildItem.BuildCommand + (self.BuildItem.Target,)
603         self.BuildTread = Thread(target=self._CommandThread, args=(Command, self.BuildItem.WorkingDir))
604         self.BuildTread.setName("build thread")
605         self.BuildTread.setDaemon(False)
606         self.BuildTread.start()
607
608 ## The class implementing the EDK2 build process
609 #
610 #   The build process includes:
611 #       1. Load configuration from target.txt and tools_def.txt in $(WORKSPACE)/Conf
612 #       2. Parse DSC file of active platform
613 #       3. Parse FDF file if any
614 #       4. Establish build database, including parse all other files (module, package)
615 #       5. Create AutoGen files (C code file, depex file, makefile) if necessary
616 #       6. Call build command
617 #
618 class Build():
619     ## Constructor
620     #
621     # Constructor will load all necessary configurations, parse platform, modules
622     # and packages and the establish a database for AutoGen.
623     #
624     #   @param  Target              The build command target, one of gSupportedTarget
625     #   @param  WorkspaceDir        The directory of workspace
626     #   @param  Platform            The DSC file of active platform
627     #   @param  Module              The INF file of active module, if any
628     #   @param  Arch                The Arch list of platform or module
629     #   @param  ToolChain           The name list of toolchain
630     #   @param  BuildTarget         The "DEBUG" or "RELEASE" build
631     #   @param  FlashDefinition     The FDF file of active platform
632     #   @param  FdList=[]           The FD names to be individually built
633     #   @param  FvList=[]           The FV names to be individually built
634     #   @param  MakefileType        The type of makefile (for MSFT make or GNU make)
635     #   @param  SpawnMode           Indicate multi-thread build mode
636     #   @param  ThreadNumber        The maximum number of thread if in multi-thread build mode
637     #
638     def __init__(self, Target, WorkspaceDir, Platform, Module, Arch, ToolChain,
639                  BuildTarget, FlashDefinition, FdList=[], FvList=[],
640                  MakefileType="nmake", SpawnMode=False, ThreadNumber=2,
641                  SkipAutoGen=False, Reparse=False, SkuId=None):
642
643         self.WorkspaceDir = WorkspaceDir
644         os.chdir(self.WorkspaceDir)
645
646         self.Target         = Target
647         self.PlatformFile   = Platform
648         self.ModuleFile     = Module
649         self.ArchList       = Arch
650         self.ToolChainList  = ToolChain
651         self.BuildTargetList= BuildTarget
652         self.Fdf            = FlashDefinition
653         self.FdList         = FdList
654         self.FvList         = FvList
655         self.MakefileType   = MakefileType
656         self.SpawnMode      = SpawnMode
657         self.ThreadNumber   = ThreadNumber
658         self.SkipAutoGen    = SkipAutoGen
659         self.Reparse        = Reparse
660         self.SkuId          = SkuId
661
662         self.TargetTxt      = TargetTxtClassObject()
663         self.ToolDef        = ToolDefClassObject()
664         self.Db             = WorkspaceDatabase(None, GlobalData.gGlobalDefines, self.Reparse)
665         self.BuildDatabase  = self.Db.BuildObject
666         self.Platform       = None
667
668         # print dot charater during doing some time-consuming work
669         self.Progress = Utils.Progressor()
670
671         # parse target.txt, tools_def.txt, and platform file
672         #self.RestoreBuildData()
673         self.LoadConfiguration()
674         self.InitBuild()
675
676         # print current build environment and configuration
677         EdkLogger.quiet("%-24s = %s" % ("WORKSPACE", os.environ["WORKSPACE"]))
678         EdkLogger.quiet("%-24s = %s" % ("EDK_SOURCE", os.environ["EDK_SOURCE"]))
679         EdkLogger.quiet("%-24s = %s" % ("EFI_SOURCE", os.environ["EFI_SOURCE"]))
680         EdkLogger.quiet("%-24s = %s" % ("EDK_TOOLS_PATH", os.environ["EDK_TOOLS_PATH"]))
681
682         EdkLogger.info('\n%-24s = %s' % ("TARGET_ARCH", ' '.join(self.ArchList)))
683         EdkLogger.info('%-24s = %s' % ("TARGET", ' '.join(self.BuildTargetList)))
684         EdkLogger.info('%-24s = %s' % ("TOOL_CHAIN_TAG", ' '.join(self.ToolChainList)))
685
686         EdkLogger.info('\n%-24s = %s' % ("Active Platform", self.PlatformFile))
687
688         if self.Fdf != None and self.Fdf != "":
689             EdkLogger.info('%-24s = %s' % ("Flash Image Definition", self.Fdf))
690
691         if self.ModuleFile != None and self.ModuleFile != "":
692             EdkLogger.info('%-24s = %s' % ("Active Module", self.ModuleFile))
693
694         if self.SpawnMode:
695             EdkLogger.verbose('%-24s = %s' % ("Max Thread Number", self.ThreadNumber))
696
697         self.Progress.Start("\nProcessing meta-data")
698
699     ## Load configuration
700     #
701     #   This method will parse target.txt and get the build configurations.
702     #
703     def LoadConfiguration(self):
704         #
705         # Check target.txt and tools_def.txt and Init them
706         #
707         BuildConfigurationFile = os.path.normpath(os.path.join(self.WorkspaceDir, gBuildConfiguration))
708         if os.path.isfile(BuildConfigurationFile) == True:
709             StatusCode = self.TargetTxt.LoadTargetTxtFile(BuildConfigurationFile)
710
711             ToolDefinitionFile = self.TargetTxt.TargetTxtDictionary[DataType.TAB_TAT_DEFINES_TOOL_CHAIN_CONF]
712             if ToolDefinitionFile == '':
713                 ToolDefinitionFile = gToolsDefinition
714             ToolDefinitionFile = os.path.normpath(os.path.join(self.WorkspaceDir, ToolDefinitionFile))
715             if os.path.isfile(ToolDefinitionFile) == True:
716                 StatusCode = self.ToolDef.LoadToolDefFile(ToolDefinitionFile)
717             else:
718                 EdkLogger.error("build", FILE_NOT_FOUND, ExtraData=ToolDefinitionFile)
719         else:
720             EdkLogger.error("build", FILE_NOT_FOUND, ExtraData=BuildConfigurationFile)
721
722         # if no ARCH given in command line, get it from target.txt
723         if self.ArchList == None or len(self.ArchList) == 0:
724             self.ArchList = self.TargetTxt.TargetTxtDictionary[DataType.TAB_TAT_DEFINES_TARGET_ARCH]
725
726         # if no build target given in command line, get it from target.txt
727         if self.BuildTargetList == None or len(self.BuildTargetList) == 0:
728             self.BuildTargetList = self.TargetTxt.TargetTxtDictionary[DataType.TAB_TAT_DEFINES_TARGET]
729
730         # if no tool chain given in command line, get it from target.txt
731         if self.ToolChainList == None or len(self.ToolChainList) == 0:
732             self.ToolChainList = self.TargetTxt.TargetTxtDictionary[DataType.TAB_TAT_DEFINES_TOOL_CHAIN_TAG]
733             if self.ToolChainList == None or len(self.ToolChainList) == 0:
734                 EdkLogger.error("build", RESOURCE_NOT_AVAILABLE, ExtraData="No toolchain given. Don't know how to build.\n")
735
736         # check if the tool chains are defined or not
737         NewToolChainList = []
738         for ToolChain in self.ToolChainList:
739             if ToolChain not in self.ToolDef.ToolsDefTxtDatabase[TAB_TOD_DEFINES_TOOL_CHAIN_TAG]:
740                 EdkLogger.warn("build", "Tool chain [%s] is not defined" % ToolChain)
741             else:
742                 NewToolChainList.append(ToolChain)
743         # if no tool chain available, break the build
744         if len(NewToolChainList) == 0:
745             EdkLogger.error("build", RESOURCE_NOT_AVAILABLE,
746                             ExtraData="[%s] not defined. No toolchain available for build!\n" % ", ".join(self.ToolChainList))
747         else:
748             self.ToolChainList = NewToolChainList
749
750         if self.ThreadNumber == None or self.ThreadNumber == "":
751             self.ThreadNumber = self.TargetTxt.TargetTxtDictionary[DataType.TAB_TAT_DEFINES_MAX_CONCURRENT_THREAD_NUMBER]
752             if self.ThreadNumber == '':
753                 self.ThreadNumber = 0
754             else:
755                 self.ThreadNumber = int(self.ThreadNumber, 0)
756
757         if self.ThreadNumber == 0:
758             self.SpawnMode = False
759         elif self.TargetTxt.TargetTxtDictionary[DataType.TAB_TAT_DEFINES_MULTIPLE_THREAD].lower() in ["enable", "true"]:
760             self.SpawnMode = True
761
762         if self.PlatformFile == None:
763             self.PlatformFile = self.TargetTxt.TargetTxtDictionary[DataType.TAB_TAT_DEFINES_ACTIVE_PLATFORM]
764             if self.PlatformFile == None or self.PlatformFile == "":
765                 WorkingDirectory = os.getcwd()
766                 FileList = glob.glob(os.path.normpath(os.path.join(WorkingDirectory, '*.dsc')))
767                 FileNum = len(FileList)
768                 if FileNum >= 2:
769                     EdkLogger.error("build", OPTION_MISSING,
770                                     ExtraData="There are %d DSC files in %s. Use '-p' to specify one.\n" % (FileNum, WorkingDirectory))
771                 elif FileNum == 1:
772                     self.PlatformFile = NormFile(FileList[0], self.WorkspaceDir)
773             else:
774                 self.PlatformFile = NormFile(self.PlatformFile, self.WorkspaceDir)
775
776     ## Initialize build configuration
777     #
778     #   This method will parse DSC file and merge the configurations from
779     #   command line and target.txt, then get the final build configurations.
780     #
781     def InitBuild(self):
782         if self.PlatformFile == None or self.PlatformFile == "":
783             EdkLogger.error("build", ATTRIBUTE_NOT_AVAILABLE,
784                             ExtraData="No active platform specified in target.txt or command line! Nothing can be built.\n")
785         if not os.path.isfile(self.PlatformFile):
786             EdkLogger.error("AutoGen", FILE_NOT_FOUND, ExtraData = self.PlatformFile)
787
788         # create metafile database
789         self.Db.InitDatabase()
790
791         # we need information in platform description file to determine how to build
792         self.Platform = self.BuildDatabase[self.PlatformFile, 'COMMON']
793         if self.Fdf != None:
794             self.Fdf = NormFile(self.Fdf, self.WorkspaceDir)
795         else:
796             self.Fdf = self.Platform.FlashDefinition
797
798         if self.SkuId == None or self.SkuId == '':
799             self.SkuId = self.Platform.SkuName
800
801         # check FD/FV build target
802         if self.Fdf == None or self.Fdf == "":
803             if self.FdList != []:
804                 EdkLogger.info("No flash definition file found. FD [%s] will be ignored." % " ".join(self.FdList))
805                 self.FdList = []
806             if self.FvList != []:
807                 EdkLogger.info("No flash definition file found. FV [%s] will be ignored." % " ".join(self.FvList))
808                 self.FvList = []
809
810         #
811         # Merge Arch
812         #
813         if self.ArchList == None or len(self.ArchList) == 0:
814             ArchList = set(self.Platform.SupArchList)
815         else:
816             ArchList = set(self.ArchList) & set(self.Platform.SupArchList)
817         if len(ArchList) == 0:
818             EdkLogger.error("build", PARAMETER_INVALID,
819                             ExtraData = "Active platform supports [%s] only, but [%s] is given."
820                                         % (" ".join(self.Platform.SupArchList), " ".join(self.ArchList)))
821         elif len(ArchList) != len(self.ArchList):
822             SkippedArchList = set(self.ArchList).symmetric_difference(set(self.Platform.SupArchList))
823             EdkLogger.verbose("\nArch [%s] is ignored because active platform supports [%s] but [%s] is specified !"
824                            % (" ".join(SkippedArchList), " ".join(self.Platform.SupArchList), " ".join(self.ArchList)))
825         self.ArchList = tuple(ArchList)
826
827         # Merge build target
828         if self.BuildTargetList == None or len(self.BuildTargetList) == 0:
829             BuildTargetList = self.Platform.BuildTargets
830         else:
831             BuildTargetList = list(set(self.BuildTargetList) & set(self.Platform.BuildTargets))
832         if BuildTargetList == []:
833             EdkLogger.error("build", PARAMETER_INVALID, "Active platform only supports [%s], but [%s] is given"
834                                 % (" ".join(self.Platform.BuildTargets), " ".join(self.BuildTargetList)))
835         self.BuildTargetList = BuildTargetList
836
837     ## Build a module or platform
838     #
839     # Create autogen code and makfile for a module or platform, and the launch
840     # "make" command to build it
841     #
842     #   @param  Target                      The target of build command
843     #   @param  Platform                    The platform file
844     #   @param  Module                      The module file
845     #   @param  BuildTarget                 The name of build target, one of "DEBUG", "RELEASE"
846     #   @param  ToolChain                   The name of toolchain to build
847     #   @param  Arch                        The arch of the module/platform
848     #   @param  CreateDepModuleCodeFile     Flag used to indicate creating code
849     #                                       for dependent modules/Libraries
850     #   @param  CreateDepModuleMakeFile     Flag used to indicate creating makefile
851     #                                       for dependent modules/Libraries
852     #
853     def _Build(self, Target, AutoGenObject, CreateDepsCodeFile=True, CreateDepsMakeFile=True):
854         if AutoGenObject == None:
855             return False
856
857         # skip file generation for cleanxxx targets, run and fds target
858         if Target not in ['clean', 'cleanlib', 'cleanall', 'run', 'fds']:
859             # for target which must generate AutoGen code and makefile
860             if not self.SkipAutoGen or Target == 'genc':
861                 self.Progress.Start("Generating code")
862                 AutoGenObject.CreateCodeFile(CreateDepsCodeFile)
863                 self.Progress.Stop("done!")
864             if Target == "genc":
865                 return True
866
867             if not self.SkipAutoGen or Target == 'genmake':
868                 self.Progress.Start("Generating makefile")
869                 AutoGenObject.CreateMakeFile(CreateDepsMakeFile)
870                 self.Progress.Stop("done!")
871             if Target == "genmake":
872                 return True
873         else:
874             # always recreate top/platform makefile when clean, just in case of inconsistency
875             AutoGenObject.CreateMakeFile(False)
876
877         BuildCommand = AutoGenObject.BuildCommand
878         if BuildCommand == None or len(BuildCommand) == 0:
879             EdkLogger.error("build", OPTION_MISSING, ExtraData="No MAKE command found for [%s, %s, %s]" % Key)
880
881         BuildCommand = BuildCommand + (Target,)
882         LaunchCommand(BuildCommand, AutoGenObject.MakeFileDir)
883         return True
884
885     ## Build active platform for different build targets and different tool chains
886     #
887     def _BuildPlatform(self):
888         for BuildTarget in self.BuildTargetList:
889             for ToolChain in self.ToolChainList:
890                 Wa = WorkspaceAutoGen(
891                         self.WorkspaceDir,
892                         self.Platform,
893                         BuildTarget,
894                         ToolChain,
895                         self.ArchList,
896                         self.BuildDatabase,
897                         self.TargetTxt,
898                         self.ToolDef,
899                         self.Fdf,
900                         self.FdList,
901                         self.FvList,
902                         self.SkuId
903                         )
904                 self.Progress.Stop("done!")
905                 self._Build(self.Target, Wa)
906
907     ## Build active module for different build targets, different tool chains and different archs
908     #
909     def _BuildModule(self):
910         for BuildTarget in self.BuildTargetList:
911             for ToolChain in self.ToolChainList:
912                 #
913                 # module build needs platform build information, so get platform
914                 # AutoGen first
915                 #
916                 Wa = WorkspaceAutoGen(
917                         self.WorkspaceDir,
918                         self.Platform,
919                         BuildTarget,
920                         ToolChain,
921                         self.ArchList,
922                         self.BuildDatabase,
923                         self.TargetTxt,
924                         self.ToolDef,
925                         self.Fdf,
926                         self.FdList,
927                         self.FvList,
928                         self.SkuId
929                         )
930                 Wa.CreateMakeFile(False)
931                 self.Progress.Stop("done!")
932                 MaList = []
933                 for Arch in self.ArchList:
934                     Ma = ModuleAutoGen(Wa, self.ModuleFile, BuildTarget, ToolChain, Arch, self.PlatformFile)
935                     if Ma == None: continue
936                     MaList.append(Ma)
937                     self._Build(self.Target, Ma)
938                 if MaList == []:
939                     EdkLogger.error(
940                                 'build',
941                                 BUILD_ERROR,
942                                 "Module for [%s] is not employed by active platform" % ', '.join(self.ArchList),
943                                 ExtraData=self.ModuleFile
944                                 )
945
946     ## Build a platform in multi-thread mode
947     #
948     def _MultiThreadBuildPlatform(self):
949         for BuildTarget in self.BuildTargetList:
950             for ToolChain in self.ToolChainList:
951                 Wa = WorkspaceAutoGen(
952                         self.WorkspaceDir,
953                         self.Platform,
954                         BuildTarget,
955                         ToolChain,
956                         self.ArchList,
957                         self.BuildDatabase,
958                         self.TargetTxt,
959                         self.ToolDef,
960                         self.Fdf,
961                         self.FdList,
962                         self.FvList,
963                         self.SkuId
964                         )
965                 Wa.CreateMakeFile(False)
966
967                 # multi-thread exit flag
968                 ExitFlag = threading.Event()
969                 ExitFlag.clear()
970                 for Arch in self.ArchList:
971                     Pa = PlatformAutoGen(Wa, self.PlatformFile, BuildTarget, ToolChain, Arch)
972                     if Pa == None:
973                         continue
974                     for Module in Pa.Platform.Modules:
975                         # Get ModuleAutoGen object to generate C code file and makefile
976                         Ma = ModuleAutoGen(Wa, Module, BuildTarget, ToolChain, Arch, self.PlatformFile)
977                         if Ma == None:
978                             continue
979                         # Not to auto-gen for targets 'clean', 'cleanlib', 'cleanall', 'run', 'fds'
980                         if self.Target not in ['clean', 'cleanlib', 'cleanall', 'run', 'fds']:
981                             # for target which must generate AutoGen code and makefile
982                             if not self.SkipAutoGen or self.Target == 'genc':
983                                 Ma.CreateCodeFile(True)
984                             if self.Target == "genc":
985                                 continue
986
987                             if not self.SkipAutoGen or self.Target == 'genmake':
988                                 Ma.CreateMakeFile(True)
989                             if self.Target == "genmake":
990                                 continue
991                         self.Progress.Stop("done!")
992                         # Generate build task for the module
993                         Bt = BuildTask.New(ModuleMakeUnit(Ma, self.Target))
994                         # Break build if any build thread has error
995                         if BuildTask.HasError():
996                             # we need a full version of makefile for platform
997                             ExitFlag.set()
998                             BuildTask.WaitForComplete()
999                             Pa.CreateMakeFile(False)
1000                             EdkLogger.error("build", BUILD_ERROR, BuildTask.GetErrorMessage())
1001                         # Start task scheduler
1002                         if not BuildTask.IsOnGoing():
1003                             BuildTask.StartScheduler(self.ThreadNumber, ExitFlag)
1004
1005                     # in case there's an interruption. we need a full version of makefile for platform
1006                     Pa.CreateMakeFile(False)
1007                     if BuildTask.HasError():
1008                         EdkLogger.error("build", BUILD_ERROR, BuildTask.GetErrorMessage())
1009
1010                 #
1011                 # All modules have been put in build tasks queue. Tell task scheduler
1012                 # to exit if all tasks are completed
1013                 #
1014                 ExitFlag.set()
1015                 BuildTask.WaitForComplete()
1016
1017                 #
1018                 # Check for build error, and raise exception if one
1019                 # has been signaled.
1020                 #
1021                 if BuildTask.HasError():
1022                     EdkLogger.error("build", BUILD_ERROR, BuildTask.GetErrorMessage())
1023
1024                 # Generate FD image if there's a FDF file found
1025                 if self.Fdf != '' and self.Target in ["", "all", "fds"]:
1026                     LaunchCommand(Wa.BuildCommand + ("fds",), Wa.MakeFileDir)
1027
1028     ## Generate GuidedSectionTools.txt in the FV directories.
1029     #
1030     def CreateGuidedSectionToolsFile(self):
1031         for Arch in self.ArchList:
1032             for BuildTarget in self.BuildTargetList:
1033                 for ToolChain in self.ToolChainList:
1034                     FvDir = os.path.join(
1035                                 self.WorkspaceDir,
1036                                 self.Platform.OutputDirectory,
1037                                 '_'.join((BuildTarget, ToolChain)),
1038                                 'FV'
1039                                 )
1040                     if not os.path.exists(FvDir):
1041                         continue
1042                     # Build up the list of supported architectures for this build
1043                     prefix = '%s_%s_%s_' % (BuildTarget, ToolChain, Arch)
1044
1045                     # Look through the tool definitions for GUIDed tools
1046                     guidAttribs = []
1047                     for (attrib, value) in self.ToolDef.ToolsDefTxtDictionary.iteritems():
1048                         if attrib.upper().endswith('_GUID'):
1049                             split = attrib.split('_')
1050                             thisPrefix = '_'.join(split[0:3]) + '_'
1051                             if thisPrefix == prefix:
1052                                 guid = self.ToolDef.ToolsDefTxtDictionary[attrib]
1053                                 guid = guid.lower()
1054                                 toolName = split[3]
1055                                 path = '_'.join(split[0:4]) + '_PATH'
1056                                 path = self.ToolDef.ToolsDefTxtDictionary[path]
1057                                 path = self.GetFullPathOfTool(path)
1058                                 guidAttribs.append((guid, toolName, path))
1059
1060                     # Write out GuidedSecTools.txt
1061                     toolsFile = os.path.join(FvDir, 'GuidedSectionTools.txt')
1062                     toolsFile = open(toolsFile, 'wt')
1063                     for guidedSectionTool in guidAttribs:
1064                         print >> toolsFile, ' '.join(guidedSectionTool)
1065                     toolsFile.close()
1066
1067     ## Returns the full path of the tool.
1068     #
1069     def GetFullPathOfTool (self, tool):
1070         if os.path.exists(tool):
1071             return os.path.realpath(tool)
1072         else:
1073             # We need to search for the tool using the
1074             # PATH environment variable.
1075             for dirInPath in os.environ['PATH'].split(os.pathsep):
1076                 foundPath = os.path.join(dirInPath, tool)
1077                 if os.path.exists(foundPath):
1078                     return os.path.realpath(foundPath)
1079
1080         # If the tool was not found in the path then we just return
1081         # the input tool.
1082         return tool
1083
1084     ## Launch the module or platform build
1085     #
1086     def Launch(self):
1087         if self.ModuleFile == None or self.ModuleFile == "":
1088             if not self.SpawnMode or self.Target not in ["", "all"]:
1089                 self.SpawnMode = False
1090                 self._BuildPlatform()
1091             else:
1092                 self._MultiThreadBuildPlatform()
1093             self.CreateGuidedSectionToolsFile()
1094         else:
1095             self.SpawnMode = False
1096             self._BuildModule()
1097
1098     ## Do some clean-up works when error occurred
1099     def Relinquish(self):
1100         self.DumpBuildData()
1101         Utils.Progressor.Abort()
1102         if self.SpawnMode == True:
1103             BuildTask.Abort()
1104
1105     def DumpBuildData(self):
1106         CacheDirectory = os.path.join(self.WorkspaceDir, gBuildCacheDir)
1107         Utils.CreateDirectory(CacheDirectory)
1108         Utils.DataDump(Utils.gFileTimeStampCache, os.path.join(CacheDirectory, "gFileTimeStampCache"))
1109         Utils.DataDump(Utils.gDependencyDatabase, os.path.join(CacheDirectory, "gDependencyDatabase"))
1110
1111     def RestoreBuildData(self):
1112         FilePath = os.path.join(self.WorkspaceDir, gBuildCacheDir, "gFileTimeStampCache")
1113         if Utils.gFileTimeStampCache == {} and os.path.isfile(FilePath):
1114             Utils.gFileTimeStampCache = Utils.DataRestore(FilePath)
1115             if Utils.gFileTimeStampCache == None:
1116                 Utils.gFileTimeStampCache = {}
1117
1118         FilePath = os.path.join(self.WorkspaceDir, gBuildCacheDir, "gDependencyDatabase")
1119         if Utils.gDependencyDatabase == {} and os.path.isfile(FilePath):
1120             Utils.gDependencyDatabase = Utils.DataRestore(FilePath)
1121             if Utils.gDependencyDatabase == None:
1122                 Utils.gDependencyDatabase = {}
1123
1124 def ParseDefines(DefineList=[]):
1125     DefineDict = {}
1126     if DefineList != None:
1127         for Define in DefineList:
1128             DefineTokenList = Define.split("=", 1)
1129             if len(DefineTokenList) == 1:
1130                 DefineDict[DefineTokenList[0]] = ""
1131             else:
1132                 DefineDict[DefineTokenList[0]] = DefineTokenList[1].strip()
1133     return DefineDict
1134
1135 ## Parse command line options
1136 #
1137 # Using standard Python module optparse to parse command line option of this tool.
1138 #
1139 #   @retval Opt   A optparse.Values object containing the parsed options
1140 #   @retval Args  Target of build command
1141 #
1142 def MyOptionParser():
1143     Parser = OptionParser(description=__copyright__,version=__version__,prog="build.exe",usage="%prog [options] [all|fds|genc|genmake|clean|cleanall|cleanlib|modules|libraries|run]")
1144     Parser.add_option("-a", "--arch", action="append", type="choice", choices=['IA32','X64','IPF','EBC'], dest="TargetArch",
1145         help="ARCHS is one of list: IA32, X64, IPF or EBC, which overrides target.txt's TARGET_ARCH definition. To specify more archs, please repeat this option.")
1146     Parser.add_option("-p", "--platform", action="store", type="string", dest="PlatformFile",
1147         help="Build the platform specified by the DSC file name argument, overriding target.txt's ACTIVE_PLATFORM definition.")
1148     Parser.add_option("-m", "--module", action="store", type="string", dest="ModuleFile",
1149         help="Build the module specified by the INF file name argument.")
1150     Parser.add_option("-b", "--buildtarget", action="append", type="choice", choices=['DEBUG','RELEASE'], dest="BuildTarget",
1151         help="BuildTarget is one of list: DEBUG, RELEASE, which overrides target.txt's TARGET definition. To specify more TARGET, please repeat this option.")
1152     Parser.add_option("-t", "--tagname", action="append", type="string", dest="ToolChain",
1153         help="Using the Tool Chain Tagname to build the platform, overriding target.txt's TOOL_CHAIN_TAG definition.")
1154     Parser.add_option("-x", "--sku-id", action="store", type="string", dest="SkuId",
1155         help="Using this name of SKU ID to build the platform, overriding SKUID_IDENTIFIER in DSC file.")
1156
1157     Parser.add_option("-s", "--spawn", action="store_true", type=None, dest="SpawnMode",
1158         help="If this flag is specified, as soon as a module can be built, the build will start, without waiting for AutoGen to complete remaining modules. While this option provides feedback that looks fast, due to overhead of the AutoGen function, this option is slower than letting AutoGen complete before starting the MAKE phase.")
1159     Parser.add_option("-n", action="store", type="int", dest="ThreadNumber",
1160         help="Build the platform using multi-threaded compiler, this option must combine with spawn option. The value overrides target.txt's MULTIPLE_THREAD and MAX_CONCURRENT_THREAD_NUMBER, less than 2 will disable multi-thread builds.")
1161
1162     Parser.add_option("-f", "--fdf", action="store", type="string", dest="FdfFile",
1163         help="The name of the FDF file to use, which overrides the setting in the DSC file.")
1164     Parser.add_option("-r", "--rom-image", action="append", type="string", dest="RomImage", default=[],
1165         help="The name of FD to be generated. The name must be from [FD] section in FDF file.")
1166     Parser.add_option("-i", "--fv-image", action="append", type="string", dest="FvImage", default=[],
1167         help="The name of FV to be generated. The name must be from [FV] section in FDF file.")
1168
1169     Parser.add_option("-k", "--msft", action="store_const", dest="MakefileType", const="nmake", help="Make Option: Generate only NMAKE Makefiles: Makefile")
1170     Parser.add_option("-g", "--gcc", action="store_const", dest="MakefileType", const="gmake", help="Make Option: Generate only GMAKE Makefiles: GNUmakefile")
1171     Parser.add_option("-l", "--all", action="store_const", dest="MakefileType", const="all", help="Make Option: Generate both NMAKE and GMAKE makefiles.")
1172
1173     Parser.add_option("-u", "--skip-autogen", action="store_true", dest="SkipAutoGen", help="Skip AutoGen step.")
1174     Parser.add_option("-e", "--re-parse", action="store_true", dest="Reparse", help="Re-parse all meta-data files.")
1175
1176     # Parser.add_option("-D", action="append", dest="Defines", metavar="NAME[=[VALUE]]",
1177     #     help="Define global macro which can be used in DSC/DEC/INF files.")
1178
1179     Parser.add_option("-w", "--warning-as-error", action="store_true", dest="WarningAsError", help="Treat warning in tools as error.")
1180     Parser.add_option("-j", "--log", action="store", dest="LogFile", help="Putlog in specified file as well as on console.")
1181     Parser.add_option("-q", "--quiet", action="store_true", type=None, help="Disable all messages except FATAL ERRORS.")
1182     Parser.add_option("-v", "--verbose", action="store_true", type=None, help="Turn on verbose output with informational messages printed, "\
1183                                                                                "including library instances selected, final dependency expression, "\
1184                                                                                "and warning messages, etc.")
1185     Parser.add_option("-d", "--debug", action="store", type="int", help="Enable debug messages at specified level.")
1186
1187     (Opt, Args)=Parser.parse_args()
1188     return (Opt, Args)
1189
1190 ## Tool entrance method
1191 #
1192 # This method mainly dispatch specific methods per the command line options.
1193 # If no error found, return zero value so the caller of this tool can know
1194 # if it's executed successfully or not.
1195 #
1196 #   @retval 0     Tool was successful
1197 #   @retval 1     Tool failed
1198 #
1199 def Main():
1200     StartTime = time.time()
1201
1202     # Initialize log system
1203     EdkLogger.Initialize()
1204
1205     #
1206     # Parse the options and args
1207     #
1208     (Option, Target) = MyOptionParser()
1209
1210     # Set log level
1211     if Option.verbose != None:
1212         EdkLogger.SetLevel(EdkLogger.VERBOSE)
1213     elif Option.quiet != None:
1214         EdkLogger.SetLevel(EdkLogger.QUIET)
1215     elif Option.debug != None:
1216         EdkLogger.SetLevel(Option.debug + 1)
1217     else:
1218         EdkLogger.SetLevel(EdkLogger.INFO)
1219
1220     if Option.LogFile != None:
1221         EdkLogger.SetLogFile(Option.LogFile)
1222
1223     if Option.WarningAsError == True:
1224         EdkLogger.SetWarningAsError()
1225
1226     EdkLogger.quiet(time.strftime("%H:%M:%S, %b.%d %Y ", time.localtime()) + "[00:00]" + "\n")
1227     ReturnCode = 0
1228     MyBuild = None
1229     try:
1230         if len(Target) == 0:
1231             Target = "all"
1232         elif len(Target) >= 2:
1233             EdkLogger.error("build", OPTION_NOT_SUPPORTED, "More than on targets is not supported.",
1234                             ExtraData="Please select one of: %s" %(' '.join(gSupportedTarget)))
1235         else:
1236             Target = Target[0].lower()
1237
1238         if Target not in gSupportedTarget:
1239             EdkLogger.error("build", OPTION_NOT_SUPPORTED, "Not supported target [%s]." % Target,
1240                             ExtraData="Please select one of: %s" %(' '.join(gSupportedTarget)))
1241
1242         # GlobalData.gGlobalDefines = ParseDefines(Option.Defines)
1243         #
1244         # Check environment variable: EDK_TOOLS_PATH, WORKSPACE, PATH
1245         #
1246         CheckEnvVariable()
1247         Workspace = os.getenv("WORKSPACE")
1248
1249         WorkingDirectory = os.getcwd()
1250         if Option.ModuleFile != None:
1251             Option.ModuleFile = NormFile(Option.ModuleFile, Workspace)
1252         else:
1253             FileList = glob.glob(os.path.normpath(os.path.join(WorkingDirectory, '*.inf')))
1254             FileNum = len(FileList)
1255             if FileNum >= 2:
1256                 EdkLogger.error("build", OPTION_NOT_SUPPORTED, "There are %d INF files in %s." % (FileNum, WorkingDirectory),
1257                                 ExtraData="Please use '-m <INF_FILE_PATH>' switch to choose one.")
1258             elif FileNum == 1:
1259                 Option.ModuleFile = NormFile(FileList[0], Workspace)
1260
1261         if Option.PlatformFile != None:
1262             Option.PlatformFile = NormFile(Option.PlatformFile, Workspace)
1263
1264         if Option.FdfFile != None:
1265             Option.FdfFile = NormFile(Option.FdfFile, Workspace)
1266
1267         MyBuild = Build(Target, Workspace, Option.PlatformFile, Option.ModuleFile,
1268                         Option.TargetArch, Option.ToolChain, Option.BuildTarget,
1269                         Option.FdfFile, Option.RomImage, Option.FvImage,
1270                         Option.MakefileType, Option.SpawnMode, Option.ThreadNumber,
1271                         Option.SkipAutoGen, Option.Reparse, Option.SkuId)
1272         MyBuild.Launch()
1273         MyBuild.DumpBuildData()
1274     except FatalError, X:
1275         EdkLogger.SetLevel(EdkLogger.QUIET)
1276         if MyBuild != None:
1277             # for multi-thread build exits safely
1278             MyBuild.Relinquish()
1279         if Option != None and Option.debug != None:
1280             EdkLogger.quiet(traceback.format_exc())
1281         ReturnCode = X.args[0]
1282     except Warning, X:
1283         # error from Fdf parser
1284         EdkLogger.SetLevel(EdkLogger.QUIET)
1285         if MyBuild != None:
1286             # for multi-thread build exits safely
1287             MyBuild.Relinquish()
1288         if Option != None and Option.debug != None:
1289             EdkLogger.quiet(traceback.format_exc())
1290         else:
1291             EdkLogger.error(X.ToolName, FORMAT_INVALID, File=X.FileName, Line=X.LineNumber, ExtraData=X.Message, RaiseError = False)
1292         ReturnCode = FORMAT_INVALID
1293     except KeyboardInterrupt:
1294         ReturnCode = ABORT_ERROR
1295     except:
1296         EdkLogger.SetLevel(EdkLogger.QUIET)
1297         if MyBuild != None:
1298             # for multi-thread build exits safely
1299             MyBuild.Relinquish()
1300         EdkLogger.error(
1301                     "\nbuild",
1302                     CODE_ERROR,
1303                     "Unknown fatal error",
1304                     ExtraData="Please send email to dev@buildtools.tianocore.org for help, attaching following call stack trace!\n",
1305                     RaiseError=False
1306                     )
1307         EdkLogger.quiet(traceback.format_exc())
1308         ReturnCode = CODE_ERROR
1309     finally:
1310         Utils.Progressor.Abort()
1311
1312     if MyBuild != None:
1313         MyBuild.Db.Close()
1314
1315     if ReturnCode == 0:
1316         Conclusion = "Done"
1317     elif ReturnCode == ABORT_ERROR:
1318         Conclusion = "Aborted"
1319     else:
1320         Conclusion = "Failed"
1321     FinishTime = time.time()
1322     BuildDuration = time.strftime("%M:%S", time.gmtime(int(round(FinishTime - StartTime))))
1323     EdkLogger.quiet("\n- %s -\n%s [%s]" % (Conclusion, time.strftime("%H:%M:%S, %b.%d %Y", time.localtime()), BuildDuration))
1324
1325     return ReturnCode
1326
1327 if __name__ == '__main__':
1328     r = Main()
1329     if r < 0 or r > 127: r = 127
1330     sys.exit(r)