1## @file
2# Update build revisions of the tools when performing a developer build
3#
4# This script will modife the C/Include/Common/BuildVersion.h file and the two
5# Python scripts, Python/Common/BuildVersion.py and Python/UPT/BuildVersion.py.
6# If SVN is available, the tool will obtain the current checked out version of
7# the source tree for including the the --version commands.
8
9#  Copyright (c) 2014 - 2015, Intel Corporation. All rights reserved.<BR>
10#
11#  This program and the accompanying materials
12#  are licensed and made available under the terms and conditions of the BSD License
13#  which accompanies this distribution.  The full text of the license may be found at
14#  http://opensource.org/licenses/bsd-license.php
15#
16#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
17#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
18##
19""" This program will update the BuildVersion.py and BuildVersion.h files used to set a tool's version value """
20from __future__ import absolute_import
21
22import os
23import shlex
24import subprocess
25import sys
26
27from argparse import ArgumentParser, SUPPRESS
28from tempfile import NamedTemporaryFile
29from types import IntType, ListType
30
31
32SYS_ENV_ERR = "ERROR : %s system environment variable must be set prior to running this tool.\n"
33
34__execname__ = "UpdateBuildVersions.py"
35SVN_REVISION = "$LastChangedRevision: 3 $"
36SVN_REVISION = SVN_REVISION.replace("$LastChangedRevision:", "").replace("$", "").strip()
37__copyright__ = "Copyright (c) 2014, Intel Corporation. All rights reserved."
38VERSION_NUMBER = "0.7.0"
39__version__ = "Version %s.%s" % (VERSION_NUMBER, SVN_REVISION)
40
41
42def ParseOptions():
43    """
44    Parse the command-line options.
45    The options for this tool will be passed along to the MkBinPkg tool.
46    """
47    parser = ArgumentParser(
48        usage=("%s [options]" % __execname__),
49        description=__copyright__,
50        conflict_handler='resolve')
51
52    # Standard Tool Options
53    parser.add_argument("--version", action="version",
54                        version=__execname__ + " " + __version__)
55    parser.add_argument("-s", "--silent", action="store_true",
56                        dest="silent",
57                        help="All output will be disabled, pass/fail determined by the exit code")
58    parser.add_argument("-v", "--verbose", action="store_true",
59                        dest="verbose",
60                        help="Enable verbose output")
61    # Tool specific options
62    parser.add_argument("--revert", action="store_true",
63                        dest="REVERT", default=False,
64                        help="Revert the BuildVersion files only")
65    parser.add_argument("--svn-test", action="store_true",
66                        dest="TEST_SVN", default=False,
67                        help="Test if the svn command is available")
68    parser.add_argument("--svnFlag", action="store_true",
69                        dest="HAVE_SVN", default=False,
70                        help=SUPPRESS)
71
72    return(parser.parse_args())
73
74
75def ShellCommandResults(CmdLine, Opt):
76    """ Execute the comand, returning the output content """
77    file_list = NamedTemporaryFile(delete=False)
78    filename = file_list.name
79    Results = []
80
81    returnValue = 0
82    try:
83        subprocess.check_call(args=shlex.split(CmdLine), stderr=subprocess.STDOUT, stdout=file_list)
84    except subprocess.CalledProcessError as err_val:
85        file_list.close()
86        if not Opt.silent:
87            sys.stderr.write("ERROR : %d : %s\n" % (err_val.returncode, err_val.__str__()))
88            if os.path.exists(filename):
89                sys.stderr.write("      : Partial results may be in this file: %s\n" % filename)
90            sys.stderr.flush()
91        returnValue = err_val.returncode
92
93    except IOError as (errno, strerror):
94        file_list.close()
95        if not Opt.silent:
96            sys.stderr.write("I/O ERROR : %s : %s\n" % (str(errno), strerror))
97            sys.stderr.write("ERROR : this command failed : %s\n" % CmdLine)
98            if os.path.exists(filename):
99                sys.stderr.write("      : Partial results may be in this file: %s\n" % filename)
100            sys.stderr.flush()
101        returnValue = errno
102
103    except OSError as (errno, strerror):
104        file_list.close()
105        if not Opt.silent:
106            sys.stderr.write("OS ERROR : %s : %s\n" % (str(errno), strerror))
107            sys.stderr.write("ERROR : this command failed : %s\n" % CmdLine)
108            if os.path.exists(filename):
109                sys.stderr.write("      : Partial results may be in this file: %s\n" % filename)
110            sys.stderr.flush()
111        returnValue = errno
112
113    except KeyboardInterrupt:
114        file_list.close()
115        if not Opt.silent:
116            sys.stderr.write("ERROR : Command terminated by user : %s\n" % CmdLine)
117            if os.path.exists(filename):
118                sys.stderr.write("      : Partial results may be in this file: %s\n" % filename)
119            sys.stderr.flush()
120        returnValue = 1
121
122    finally:
123        if not file_list.closed:
124            file_list.flush()
125            os.fsync(file_list.fileno())
126            file_list.close()
127
128    if os.path.exists(filename):
129        fd_ = open(filename, 'r')
130        Results = fd_.readlines()
131        fd_.close()
132        os.unlink(filename)
133
134    if returnValue > 0:
135        return returnValue
136
137    return Results
138
139
140def UpdateBuildVersionPython(Rev, UserModified, opts):
141    """ This routine will update the BuildVersion.h files in the C source tree """
142    for SubDir in ["Common", "UPT"]:
143        PyPath = os.path.join(os.environ['BASE_TOOLS_PATH'], "Source", "Python", SubDir)
144        BuildVersionPy = os.path.join(PyPath, "BuildVersion.py")
145        fd_ = open(os.path.normpath(BuildVersionPy), 'r')
146        contents = fd_.readlines()
147        fd_.close()
148        if opts.HAVE_SVN is False:
149            BuildVersionOrig = os.path.join(PyPath, "orig_BuildVersion.py")
150            fd_ = open (BuildVersionOrig, 'w')
151            for line in contents:
152                fd_.write(line)
153            fd_.flush()
154            fd_.close()
155        new_content = []
156        for line in contents:
157            if line.strip().startswith("gBUILD_VERSION"):
158                new_line = "gBUILD_VERSION = \"Developer Build based on Revision: %s\"" % Rev
159                if UserModified:
160                    new_line = "gBUILD_VERSION = \"Developer Build based on Revision: %s with Modified Sources\"" % Rev
161                new_content.append(new_line)
162                continue
163            new_content.append(line)
164
165        fd_ = open(os.path.normpath(BuildVersionPy), 'w')
166        for line in new_content:
167            fd_.write(line)
168        fd_.close()
169
170
171def UpdateBuildVersionH(Rev, UserModified, opts):
172    """ This routine will update the BuildVersion.h files in the C source tree """
173    CPath = os.path.join(os.environ['BASE_TOOLS_PATH'], "Source", "C", "Include", "Common")
174    BuildVersionH = os.path.join(CPath, "BuildVersion.h")
175    fd_ = open(os.path.normpath(BuildVersionH), 'r')
176    contents = fd_.readlines()
177    fd_.close()
178    if opts.HAVE_SVN is False:
179        BuildVersionOrig = os.path.join(CPath, "orig_BuildVersion.h")
180        fd_ = open(BuildVersionOrig, 'w')
181        for line in contents:
182            fd_.write(line)
183        fd_.flush()
184        fd_.close()
185
186    new_content = []
187    for line in contents:
188        if line.strip().startswith("#define"):
189            new_line = "#define __BUILD_VERSION \"Developer Build based on Revision: %s\"" % Rev
190            if UserModified:
191                new_line = "#define __BUILD_VERSION \"Developer Build based on Revision: %s with Modified Sources\"" % \
192                            Rev
193            new_content.append(new_line)
194            continue
195        new_content.append(line)
196
197    fd_ = open(os.path.normpath(BuildVersionH), 'w')
198    for line in new_content:
199        fd_.write(line)
200    fd_.close()
201
202
203def RevertCmd(Filename, Opt):
204    """ This is the shell command that does the SVN revert """
205    CmdLine = "svn revert %s" % Filename.replace("\\", "/").strip()
206    try:
207        subprocess.check_output(args=shlex.split(CmdLine))
208    except subprocess.CalledProcessError as err_val:
209        if not Opt.silent:
210            sys.stderr.write("Subprocess ERROR : %s\n" % err_val)
211            sys.stderr.flush()
212
213    except IOError as (errno, strerror):
214        if not Opt.silent:
215            sys.stderr.write("I/O ERROR : %d : %s\n" % (str(errno), strerror))
216            sys.stderr.write("ERROR : this command failed : %s\n" % CmdLine)
217            sys.stderr.flush()
218
219    except OSError as (errno, strerror):
220        if not Opt.silent:
221            sys.stderr.write("OS ERROR : %d : %s\n" % (str(errno), strerror))
222            sys.stderr.write("ERROR : this command failed : %s\n" % CmdLine)
223            sys.stderr.flush()
224
225    except KeyboardInterrupt:
226        if not Opt.silent:
227            sys.stderr.write("ERROR : Command terminated by user : %s\n" % CmdLine)
228            sys.stderr.flush()
229
230    if Opt.verbose:
231        sys.stdout.write("Reverted this file: %s\n" % Filename)
232        sys.stdout.flush()
233
234
235def GetSvnRevision(opts):
236    """ Get the current revision of the BaseTools/Source tree, and check if any of the files have been modified """
237    Revision = "Unknown"
238    Modified = False
239
240    if opts.HAVE_SVN is False:
241        sys.stderr.write("WARNING: the svn command-line tool is not available.\n")
242        return (Revision, Modified)
243
244    SrcPath = os.path.join(os.environ['BASE_TOOLS_PATH'], "Source")
245    # Check if there are modified files.
246    Cwd = os.getcwd()
247    os.chdir(SrcPath)
248
249    StatusCmd = "svn st -v --depth infinity --non-interactive"
250    contents = ShellCommandResults(StatusCmd, opts)
251    os.chdir(Cwd)
252    if type(contents) is ListType:
253        for line in contents:
254            if line.startswith("M "):
255                Modified = True
256                break
257
258    # Get the repository revision of BaseTools/Source
259    InfoCmd = "svn info %s" % SrcPath.replace("\\", "/").strip()
260    Revision = 0
261    contents = ShellCommandResults(InfoCmd, opts)
262    if type(contents) is IntType:
263        return 0, Modified
264    for line in contents:
265        line = line.strip()
266        if line.startswith("Revision:"):
267            Revision = line.replace("Revision:", "").strip()
268            break
269
270    return (Revision, Modified)
271
272
273def CheckSvn(opts):
274    """
275    This routine will return True if an svn --version command succeeds, or False if it fails.
276    If it failed, SVN is not available.
277    """
278    OriginalSilent = opts.silent
279    opts.silent = True
280    VerCmd = "svn --version"
281    contents = ShellCommandResults(VerCmd, opts)
282    opts.silent = OriginalSilent
283    if type(contents) is IntType:
284        if opts.verbose:
285            sys.stdout.write("SVN does not appear to be available.\n")
286            sys.stdout.flush()
287        return False
288
289    if opts.verbose:
290        sys.stdout.write("Found %s" % contents[0])
291        sys.stdout.flush()
292    return True
293
294
295def CopyOrig(Src, Dest, Opt):
296    """ Overwrite the Dest File with the Src File content """
297    try:
298        fd_ = open(Src, 'r')
299        contents = fd_.readlines()
300        fd_.close()
301        fd_ = open(Dest, 'w')
302        for line in contents:
303            fd_.write(line)
304        fd_.flush()
305        fd_.close()
306    except IOError:
307        if not Opt.silent:
308            sys.stderr.write("Unable to restore this file: %s\n" % Dest)
309            sys.stderr.flush()
310        return 1
311
312    os.remove(Src)
313    if Opt.verbose:
314        sys.stdout.write("Restored this file: %s\n" % Src)
315        sys.stdout.flush()
316
317    return 0
318
319
320def CheckOriginals(Opts):
321    """
322    If SVN was not available, then the tools may have made copies of the original BuildVersion.* files using
323    orig_BuildVersion.* for the name. If they exist, replace the existing BuildVersion.* file with the corresponding
324    orig_BuildVersion.* file.
325    Returns 0 if this succeeds, or 1 if the copy function fails. It will also return 0 if the orig_BuildVersion.* file
326    does not exist.
327    """
328    CPath = os.path.join(os.environ['BASE_TOOLS_PATH'], "Source", "C", "Include", "Common")
329    BuildVersionH = os.path.join(CPath, "BuildVersion.h")
330    OrigBuildVersionH = os.path.join(CPath, "orig_BuildVersion.h")
331    if not os.path.exists(OrigBuildVersionH):
332        return 0
333    if CopyOrig(OrigBuildVersionH, BuildVersionH, Opts):
334        return 1
335    for SubDir in ["Common", "UPT"]:
336        PyPath = os.path.join(os.environ['BASE_TOOLS_PATH'], "Source", "Python", SubDir)
337        BuildVersionPy = os.path.join(PyPath, "BuildVersion.h")
338        OrigBuildVersionPy = os.path.join(PyPath, "orig_BuildVersion.h")
339        if not os.path.exists(OrigBuildVersionPy):
340            return 0
341        if CopyOrig(OrigBuildVersionPy, BuildVersionPy, Opts):
342            return 1
343
344    return 0
345
346
347def RevertBuildVersionFiles(opts):
348    """
349    This routine will attempt to perform an SVN --revert on each of the BuildVersion.* files
350    """
351    if not opts.HAVE_SVN:
352        if CheckOriginals(opts):
353            return 1
354        return 0
355    # SVN is available
356    BuildVersionH = os.path.join(os.environ['BASE_TOOLS_PATH'], "Source", "C", "Include", "Common", "BuildVersion.h")
357    RevertCmd(BuildVersionH, opts)
358    for SubDir in ["Common", "UPT"]:
359        BuildVersionPy = os.path.join(os.environ['BASE_TOOLS_PATH'], "Source", "Python", SubDir, "BuildVersion.py")
360        RevertCmd(BuildVersionPy, opts)
361
362def UpdateRevisionFiles():
363    """ Main routine that will update the BuildVersion.py and BuildVersion.h files."""
364    options = ParseOptions()
365    # Check the working environment
366    if "WORKSPACE" not in os.environ.keys():
367        sys.stderr.write(SYS_ENV_ERR % 'WORKSPACE')
368        return 1
369    if 'BASE_TOOLS_PATH' not in os.environ.keys():
370        sys.stderr.write(SYS_ENV_ERR % 'BASE_TOOLS_PATH')
371        return 1
372    if not os.path.exists(os.environ['BASE_TOOLS_PATH']):
373        sys.stderr.write("Unable to locate the %s directory." % os.environ['BASE_TOOLS_PATH'])
374        return 1
375
376
377    options.HAVE_SVN = CheckSvn(options)
378    if options.TEST_SVN:
379        return (not options.HAVE_SVN)
380    # done processing the option, now use the option.HAVE_SVN as a flag. True = Have it, False = Don't have it.
381    if options.REVERT:
382        # Just revert the tools an exit
383        RevertBuildVersionFiles(options)
384    else:
385        # Revert any changes in the BuildVersion.* files before setting them again.
386        RevertBuildVersionFiles(options)
387        Revision, Modified = GetSvnRevision(options)
388        if options.verbose:
389            sys.stdout.write("Revision: %s is Modified: %s\n" % (Revision, Modified))
390            sys.stdout.flush()
391        UpdateBuildVersionH(Revision, Modified, options)
392        UpdateBuildVersionPython(Revision, Modified, options)
393
394    return 0
395
396
397if __name__ == "__main__":
398    sys.exit(UpdateRevisionFiles())
399
400
401