Files
DistMaker/script/jreUtils.py
Norberto Lopez 5961d129f9 Updates to DistMaker scripts:
[1] Updated variable names to reflect standardized naming convention.
[2] Added requirement check for Python 2.7 or later
[3] Added requirement check for Java 1.8 or later when building a distribution
[4] Added preliminary support for platform architectures (initial support for x64)
[5] Switched over to GNU style argument conventions rather than UNIX style conventions. Essentially DistMaker arguments require double dashes rather than single dashes.
[6] Added support for zip JREs (in addition to tar.gz files)
[7] Switched over to JRE catalog mechanism.
[7.1] A catalog file must be specified via '--jreCatalog' that lists all available JREs. See example file ./template/JreCatalog.txt
[7.2]  Eliminated file naming convention criteria for JRE file names. The only requirement is the file must end in .tar.gz or .zip
[8] Updated platform naming conventions ('Apple' → 'Macosx')
2020-05-01 23:46:23 +00:00

387 lines
14 KiB
Python

#! /usr/bin/env python
import glob
import os
import re
import subprocess
import tempfile
import logUtils
import miscUtils
from logUtils import errPrintln, regPrintln
from miscUtils import ErrorDM
# JreNode class definition
class JreNode:
def __init__(self, aArchitecture, aPlatform, aVersion, aFilePath):
self.architecture = aArchitecture
self.platform = aPlatform
self.version = aVersion
self.filePath = aFilePath
def getArchitecture(self):
"""Returns the JRE's architecture."""
return self.architecture
def getPlatform(self):
"""Returns the JRE's platform."""
return self.platform
def getVersion(self):
"""Returns the JRE's version."""
return self.version
def getFile(self):
"""Returns the (tar.gz or zip) file which contains the packaged JRE."""
return self.filePath
def getBasePathFor(aJreNode):
"""Returns the JRE (base) path that should be used to access the JRE found in the specified JreNode.
This is needed since different JRE tar.gz files have been found to have different top level paths. Using
this method ensures consistency between JRE tar.gz releases after the tar.gz file is unpacked.
Please note that legacy JREs will expand to a different path than non-legacy JREs.
"""
verArr = aJreNode.getVersion()
verStr = verArrToVerStr(verArr)
if verStr.startswith("1.") == True:
basePath = 'jre' + verStr
else:
basePath = 'jre-' + verStr
return basePath;
def getJreNodesForVerStr(aJreNodeL, aVerStr):
"""Returns the JREs matching the specified specific JRE version specification. This will return an empty
list if there is no JRE that is sufficient for the request.
aJreNodeL --- The list of valid JREs
aVerStr --- The version of interest. The version must be fully qualified.
Fully qualified is defined as having the following:
Pre Java 9 ---> Exactly 4 fields:
<A>.<B>.<C>.<D>
where:
A ---> Platform (always defined as 1)
B ---> Major (stored in minor index)
C ---> Minor version (always defined as 0)
D ---> Patch
Java 9 or later ---> 3 fields or more
<A>.<B>.<C>...
where:
A ---> The major version
B ---> The minor version
C ---> The security release
... Any extra fields are vendor specific.
An ErrorDM will be raised if the version is not fully qualified Below are examples of fully qualified fields:
'1.8.0_73'
'9.0.4'
"""
# Retrieve the target version - and ensure it is fully qualified
targVer = verStrToVerArr(aVerStr)
if len(targVer) < 3:
raise ErrorDM('The specified version is not a fully qualified version. Version: ' + aVerStr)
if targVer[0] == 1 and len(targVer) != 4:
raise ErrorDM('Legacy JRE releases require exactly 4 elements for the release. Legacy releases refer to any release before Java 9. Version: ' + aVerStr)
# Search the appropriate JREs for exact matches (of targVer)
retL = []
for aJreNode in aJreNodeL:
if aJreNode.getVersion() == targVer:
retL.append(aJreNode)
return retL;
def getJreNode(aJreNodeL, aArchStr, aPlatStr, aJvmVerSpec):
"""Returns the JRE for the appropriate platform and JRE release. If there are several possible matches then
the JRE with the latest version will be returned.
aJreNodeL --- The list of available JREs.
aArchStr --- The architecture of the JRE of interest. Architecture will typically be one of: 'x64'
aPlatStr --- The platform of the JRE of interest. Platform will typically be one of: 'linux', 'macosx', 'windows'
aJvmVerSpec --- A list of 1 or 2 items that define the range of JRE versions you are interested in. If the
list has just one item then that version will be used as the minimum version.
Method will return None if there is no JRE that is sufficient for the request. Note if you do not care about
any specific update for a major version of JAVA then just specify the major version. Example '1.8' instead of
1.8.0_73'"""
# Transform a single string to a list of size 1
if isinstance(aJvmVerSpec, basestring):
aJvmVerSpec = [aJvmVerSpec]
# Retrieve the min and max JVM versions from aJvmVerSpec
minJvmVer = None
if aJvmVerSpec != None and len(aJvmVerSpec) >= 1:
minJvmVer = verStrToVerArr(aJvmVerSpec[0])
maxJvmVer = None
if aJvmVerSpec != None and len(aJvmVerSpec) == 2:
maxJvmVer = verStrToVerArr(aJvmVerSpec[1])
if aJvmVerSpec != None and len(aJvmVerSpec) > 2:
errorMsg = 'At most only 2 elements are allowed. Number of elements specified: {}'.format(aJvmVerSpec)
raise ValueError(errorMsg)
# Evaluate all available JREs for potential matches
matchL = []
for aJreNode in aJreNodeL:
# Ensure the architecture is a match
if aArchStr != aJreNode.getArchitecture():
continue
# Ensure the platform is a match
if aPlatStr != aJreNode.getPlatform():
continue
# Ensure that the JRE's version is in range of minJvmVer and maxJvmVer
evalVer = aJreNode.getVersion()
if minJvmVer != None and isVerAfterAB(minJvmVer, evalVer) == True:
continue
if maxJvmVer != None and isVerAfterAB(evalVer, maxJvmVer) == True:
continue
matchL.append(aJreNode);
# Bail if no matches were found
if len(matchL) == 0:
return None
# Determine the best match of all the matches
bestJreNode = matchL[0]
for aJreNode in matchL:
bestVer = bestJreNode.getVersion()
testVer = aJreNode.getVersion()
for b, t in zip(bestVer, testVer):
if b == t:
continue
try:
if t > b:
bestJreNode = aJreNode
break;
except:
continue
return bestJreNode
def loadJreCatalog(aFile):
"""
Utility method that loads the specified (DistMaker) JRE catalog file and returns a list of
JREs. The list of JREs will be grouped by JVM version and each individual JRE will have the
following information:
- architecture
- platform
- file path
"""
retL = []
validArchL = ['x64']
validPlatL = ['linux', 'macosx', 'windows']
workJreVer = None;
pathS = set()
# Read the file
regPrintln('Loading JRE catalog: {}'.format(aFile))
with open(aFile, 'r') as workF:
for (lineNum, aLine) in enumerate(workF, 1):
# Skip empty lines / comments
line = aLine.strip()
if len(line) == 0 or line[0] == '#':
continue
# Parse the JRE version
tokenL = line.split(',')
if tokenL[0] == 'jre' and len(tokenL) == 2:
workJreVer = verStrToVerArr(tokenL[1])
continue
errMsgL = []
# Parse the JRE Node
if tokenL[0] == 'F' and len(tokenL) == 4:
archStr = tokenL[1]
platStr = tokenL[2]
pathStr = tokenL[3]
# Ensure the JRE version section has been declared
if workJreVer == None:
errMsg = 'JRE version section must be declared first. Skipping input: {}'.format(aLine[:-1])
errPrintln('\tERROR: [L: {}] {}'.format(lineNum, errMsg))
continue
# Ensure the path is a file
if os.path.isfile(pathStr) == False:
errMsgL += ['JRE file does not exist! Path: {}'.format(pathStr)];
# Ensure the architecture is recognized
if (archStr in validArchL) == False:
errMsgL += ['Architecture is not recognized. Valid: {} -> Input: {}'.format(validArchL, archStr)];
# Ensure the platform is recognized
if (platStr in validPlatL) == False:
errMsgL += ['Platform is not recognized. Valid: {} -> Input: {}'.format(validPlatL, platStr)];
# Ensure the reference JRE file has not been seen before
if pathStr in pathS:
errMsgL += ['JRE file has already been specified earlier! Path: {}'.format(pathStr)];
# If no errors then form the JreNode
if len(errMsgL) == 0:
retL += [JreNode(archStr, platStr, workJreVer, pathStr)]
pathS.add(pathStr)
continue
# Unrecognized line
else:
errMsgL += ['Input line is not recognized. Input: {}'.format(aLine[:-1])]
# Log the errors
for aErrMsg in errMsgL:
errPrintln('\tERROR: [L: {}] {}'.format(lineNum, aErrMsg))
regPrintln('\tLoaded JRE declarations: {}\n'.format(len(retL)))
return retL;
def normalizeJvmVerStr(aVerStr):
"""Returns a normalized JVM version corresponding to the string. Normalization consists of ensuring all of the components
of the version are separated by '.' except if applicable the third '.' is changed into '_'. This is useful so that referencing
JVM versions is consistent. Some examples of normalization are:
1.8 ---> 1.8
1.8.0.73 ---> 1.8.0_73
1.7.0_55 ---> 1.7.0_55
9.0.4 ---> 9.0.4
"""
verArr = verStrToVerArr(aVerStr)
retVerStr = verArrToVerStr(verArr)
return retVerStr
def unpackAndRenameToStandard(aJreNode, aDestPath):
"""Method that will unpack the specified JRE (tar.gz or zip) into the folder aDestPath. The unpacked JRE folder will also
be renamed to a standard convention: jre<A>.<B> where
A: represents the major (classical) version
B: represents the minor version
Hence the JRE tar.gz file: jre-8u73-linux-x64.tar.gz should be unpacked to jre1.8.73"""
# Ensure the aDestPath exists
if os.path.isdir(aDestPath) == False:
os.makedirs(aDestPath)
# Unpack to a temporary folder at aDestPath
tmpPath = tempfile.mkdtemp(dir=aDestPath)
jreFile = aJreNode.getFile()
if len(jreFile) > 4 and jreFile.upper()[-3:] == 'ZIP':
subprocess.check_call(["unzip", "-d", tmpPath, "-q", jreFile], stderr=subprocess.STDOUT)
else:
subprocess.check_call(["tar", "-C", tmpPath, "-xf", jreFile], stderr=subprocess.STDOUT)
# Rename the single extracted folder to a standard convention and move to aDestPath
fileL = glob.glob(os.path.join(tmpPath, '*'))
if len(fileL) != 1 or os.path.isdir(fileL[0]) == False:
errPrintln('Fatal error while unpacking JRE package file. Did not resolve to single folder! Terminating build process!')
errPrintln('\tJRE package file: ' + aJreNode.getFile())
errPrintln('\tUnpacked files: ' + str(fileL))
exit(-1)
jreBasePath = getBasePathFor(aJreNode)
targPath = os.path.join(aDestPath, jreBasePath)
os.rename(fileL[0], targPath)
# Warn if hidden files are located (and remove them)
# It appears that some JREs (Macosx specific? may include spurious hidden files.)
for aName in os.listdir(tmpPath):
spurFile = os.path.join(tmpPath, aName)
errPrintln('\tFound spurious (hidden) file: ' + spurFile)
errPrintln('\t\tAuto removing file: ' + spurFile)
os.remove(spurFile)
# Remove the temporary path
os.rmdir(tmpPath)
def validateJreVersionSpec(aVerSpec):
"""Method that ensures that the specified aVerSpec is a valid definition. A valid JVM version spec can be composed of the following
- None: The jreVersion was not specified and should default to DistMaker's default values.
- A list of 1: The value defines the minimum allowable JRE version
- A list of 2: The 2 value defines the 2 JRE versions. The first is the minimum value. The second is the maximum value
Furthermore each JRE version must be a valid specification. Examples of valid specifications are:
1.7, 1.8.0, 1.8.0_73"""
# Bail if no version was specified
if aVerSpec == None:
return
# Ensure the number of arguments are correct. Must be 1 or 2
if (len(aVerSpec) in [1, 2]) == False:
raise ErrorDM('The parameter jreVersion can either be 1 or 2 values. The first value is the minVersion and (if specified) the later is the maxVersion. Values provided: ' + str(len(aVerSpec)))
# Ensure the minVer is valid
minVer = verStrToVerArr(aVerSpec[0])
if minVer == None:
raise ErrorDM('The specified string: "' + aVerSpec[0] + ' is not a valid JRE version specification.')
# Ensure the minVer is not before Java 1.7
if isVerAfterAB([1, 7], minVer) == True:
raise ErrorDM('In the parameter jreVersion, the minVer ({}) must be later than 1.7.'.format(aVerSpec[0]))
# Bail if only the minVer was specified
if len(aVerSpec) == 1:
return
# Ensure the maxVer is valid and is after minVer
maxVer = verStrToVerArr(aVerSpec[1])
if maxVer == None:
raise ErrorDM('The specified string: "' + aVerSpec[1] + ' is not a valid JRE version specification.')
# Ensure the minVer is not later than the maxVer. Note it is ok if it is equal
if isVerAfterAB(minVer, maxVer) == True:
raise ErrorDM('In the parameter jreVersion, the minVer ({}) is later than the maxVer ({}).'.format(aVerSpec[0], aVerSpec[1]))
def verStrToVerArr(aVerStr):
"""Utility method to convert a jvm version string to the equivalent integral version (list) component. Each component in the array will be integral.
Thus the following will get transformed to:
'1.7.0 ---> [1, 7, 0]
'1.8' ---> [1, 8]
'1.8.0_73' ---> [1, 8, 0, 73]"""
verStrL = re.compile("[._]").split(aVerStr)
# Transform from string list to integer list
try:
retVerL = [int(val) for val in verStrL]
except:
raise ErrorDM('Invalid JVM version: ' + aVerStr)
return retVerL
def verArrToVerStr(aVerL):
"""Utility method to convert an integral version (list) to the equivalent jvm version string. Each component in the list must be integral.
Thus the following will get transformed to:
[1, 8] ---> '1.8'
[1, 8, 0, 73] ---> '1.8.0_73'
[9, 0, 4] ---> '9.0.4'"""
# Transform from integer list to string
if len(aVerL) <= 3:
retVerStr = ".".join(str(x) for x in aVerL)
return retVerStr
else:
retVerStr = ".".join(str(x) for x in aVerL[0:3])
retVerStr += '_' + ".".join(str(x) for x in aVerL[3:])
return retVerStr
def isVerAfterAB(aJvmVerA, aJvmVerB):
"""Returns True if the specified JvmVerA is later than aJvmVerB. Both versions should be composed of a list of integers. Typically this list would be formed
from the method verStrToVerArr. Note that if one version has more detail than the other and all others are previous components are equivalent then the more
detailed version will be considered later (since it can be assumed that the missing details refer to the very first issue)"""
for a, b in zip(aJvmVerA, aJvmVerB):
if a == b:
continue
if a > b:
return True
else:
return False
# Since all other components are equal then verA is better than verB only if it is more detailed
if len(aJvmVerA) > len(aJvmVerB):
return True
return False