Rev 7070 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
package com.erggroup.buildtool.ripple;import java.io.File;import java.sql.SQLException;import java.util.ArrayList;import java.util.Comparator;import java.util.HashMap;import java.util.Iterator;import java.util.LinkedHashSet;import java.util.Vector;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.erggroup.buildtool.ripple.ReleaseManager.BuildReason;import com.erggroup.buildtool.smtp.CreateUrls;import com.erggroup.buildtool.smtp.Smtpsend;import com.erggroup.buildtool.utilities.MutableInt;import com.erggroup.buildtool.utilities.XmlBuilder;import com.erggroup.buildtool.utilities.utilities;public class Package{/*** name of package, must not contain spaces** @attribute*/String mName = new String();/*** package scope** @attribute*/String mExtension = new String();/*** instance identifier** @attribute*/public String mVersion = new String();/*** If this package is a candidate for building, then this value will be calculated* and will be used if the package is selected for building*/public String mNextVersion = null;/*** Version string as specified by the user. Used with a ripple type of 'F'*/String mFixedVersion = new String();/*** unique identifier* daemon builds = mName + mExtension* escrow builds = mName + mVersion + mExtension** @attribute*/public String mAlias = new String();/*** Version Control System Tag** @attribute*/String mVcsTag = new String();/*** build standards** @attribute*/public Vector<BuildStandard> mBuildStandardCollection = new Vector<BuildStandard>();/*** GBE_MACHTYPE used to build generic packages for this baseline only has* meaning in the daemon build, not the escrow build accessed by* BuildStandard::getPlatform, getBuildStandard** @attribute*/public static String mGenericMachtype = System.getenv("GBE_MACHTYPE");/*** build dependencies by package alias** @attribute*/public Vector<String> mDependencyCollection = new Vector<String>();/*** primary package version key pv_id in database** @attribute*/public int mId;/*** indication of the nature of change** @attribute*/Package.VersionNumberingStandard mChangeType = new VersionNumberingStandard();/*** determines what field is rippled on a package version whose dependencies* have changed** @attribute*/Package.VersionNumberingStandard mRippleField = new VersionNumberingStandard();/*** interested owners** @attribute*/private Vector<String> mBuildFailureEmailCollection = new Vector<String>();/*** when true will trigger unit tests as part of the package build phase in* daemon mode** @attribute*/public boolean mHasAutomatedUnitTests = false;/*** when true, do not ripple this package through packages which are* dependent upon it in daemon mode** @attribute*/public boolean mAdvisoryRipple = false;/*** Pegged packages will:* - Not be rippled* - Not be test buildable* - Dependencies will not be considered* Daemon Mode:* Are not built in this release* Are imported with out consideration as to their dependencies* Must exist in dpkg_archive - will not be built if not present* Escrow Mode:* Have no special consideration* The package and its dependencies will be built** Packages imported into the release from an SDK will be treated as if they were* pegged by the built system.**/public boolean mIsPegged = false;public boolean mIsSdk = false;/*** Unbuildable packages will not be candidates for a ripple or a rebuild* If they do not exist in dpkg_archive, then then will cause an error*/public boolean mIsBuildable = true;/*** determines the build file the package is built in, or not* <br> 1 Post Plan: build file - Result of planning* <br> 2 Post Plan: Future build requirement* <br> 3 Post Plan: Package has no build requirement* <br> 0 not yet processed (initial value)* <br> -1 not reproducible* <br> -2 escrow: not reproducible on the build platforms configured for this release* <br> -3 daemon: do not ripple* <br> -4 directly dependent on package versions not in the baseline* <br> -5 indirectly dependent on package versions which are not reproducible due to detected fault* <br> -6 circular dependency* <br> -7 Pegged or SDK Imported package not in dpkg_archive* <br> -8 Pegged or SDK Imported package* <br> -9 Rejected Daemon Instruction* <br> -10 UnBuildable Package not in dpkg_archive* <br> -11 Marked as RippleStop**/int mBuildFile = 0;/** Retained reason the package will not be built* Save negative values as mBuildFile* @attribute*/int mNoBuildReason = 0;/*** build dependencies by package* Calculated from information in mDependencyCollection** @attribute*/Vector<Package> mPackageDependencyCollection = new Vector<Package>();/*** used for escrow build purposes set true when a package has been processed** @attribute*/boolean mProcessed = false;/*** Reason that a package is being built*/public BuildReason mBuildReason = null;/*** set true for WIP package versions only used in daemon mode** @attribute*/public boolean mDirectlyPlanned = false;/*** set true when it is determined to be ripple built** @attribute*/boolean mIndirectlyPlanned = false;/*** non zero instruction number when it is determined to be ripple built by force** @attribute*/public int mForcedRippleInstruction = 0;/*** test build - non zero instruction number when it is determined to be test built** @attribute*/public int mTestBuildInstruction = 0;/*** build dependencies by pv_id (-1 or not used for planned dependencies)** @attribute*/public Vector<Integer> mDependencyIDCollection = new Vector<Integer>();/*** unique pkg_id in the database used for querying package version existence* in the database in daemon mode** @attribute*/public int mPid;/*** maximum major number supported for determining ripple number** @attribute*/int mMajorLimit;/*** maximum minor number supported for determining ripple number** @attribute*/int mMinorLimit;/*** maximum patch number supported for determining ripple number** @attribute*/int mPatchLimit;/*** maximum build number number supported for determining ripple number** @attribute*/int mBuildLimit;/*** Logger** @attribute*/private static final Logger mLogger = LoggerFactory.getLogger(Package.class);/*** dpkg archive location - The writable dpkg_archive** @attribute*/public static String mGbeDpkg = System.getenv("GBE_DPKG");/*** A physically close replica of dpkg_archive* Is not writable*/public static final String mGbeDpkgReplica = System.getenv("GBE_DPKG_REPLICA");/*** Exception message used upon detection an archive does not exist Seems* this is a rare but transient and recoverable scenario** @attribute*/public static final String mRecoverable = "dpkg_archive does not exist, recovery will be attempted";/*** true if the package exists in the package archive (dpkg_archive)** @attribute*/private boolean mArchivalExistence = true;/*** when true will trigger source control interaction eg labelling** @attribute*/public boolean mRequiresSourceControlInteraction = true;/*** when true has been checked for circular dependency** @attribute*/boolean mCheckedCircularDependency = false;/*** when true has circular dependency, or in the process of detecting a circular dependency** @attribute*/boolean mHasCircularDependency = false;/*** Bread crumb used to detect circular dependencies* 0 - Normal state* 1 - Crumb has been set* 2 - Crumb is a part of a circular dependency* 3 - Crumb is the start/end of the loop*/int mBreadCrumb = 0;/*** Planned packages (WIPS) need calculate a new version number* This should be based on the Version of the package that the WIP was based upon*/public String mPrevVersion;/** Used to preserve the basic ordering of the package list(s)* Only valid for the duration of the comparison sorts*/int mSeqId;/** Indicates that the package not be ripple built at this time* Sequence is:* User sets to 's'* This buildtool will set to 'w' when a ripple has been detected* User resets to NULL when the ripple can be resumed. This is stored as an 'n'**/public char mRippleStop;/*** The calculated ripple build plan for this package* Will not be calculated for all packages, just those of interest* All packages that can (not must) be built when this package is built* The list will not include packages that cannot be built at the moment** Does not include this package*/public ArrayList<Package> mRipplePlan = null;/** The duration (seconds) of the last build of this package* Used to estimate the cost of building the package* A WIP packages gets the buildTime of the package its based upon*/public int mBuildTime;/** Calculated time ( seconds) that is may take to build this package and then ripple packages that depend on it* both directly and indirectly. Based on mRipplePlan.*/public int mRippleTime = 0;/** Used in the calculation of a ripple plan**/public int mRippleOrder = 0;/** Set to indicate that the package is NOT a part of the full release set* The package is a WIP, TestBuild or a ForcedRipple** Used to simplify detection of such packages and to limit processing of these packages* May be cleared if the package is added to the full set*/public boolean mIsNotReleased = false;/** Used by some algorithms for various indications**/public boolean mIsProcessed = false;/*** Constructor* Used to create planned packages. In these packages the change_type is significant** @param pkg_id Package Name Identifier* @param pv_id Package Version Id* @param pkg_name Package Name* @param pkg_version Package Version* @param v_ext Package Suffix* @param alias Package Alias* @param pkg_vcs_tag Vcs Tag* @param ripple_field Ripple Field* @param change_type Change Type*/public Package(int pkg_id, int pv_id, String pkg_name, String pkg_version, String v_ext, String alias, String pkg_vcs_tag, char ripple_field, char change_type){mLogger.debug("Package 1: pv_id " + pv_id + " pkg_name " + pkg_name + " v_ext " + v_ext + " alias " + alias+ " pkg_vcs_tag " + pkg_vcs_tag + " change_type " + change_type);mId = pv_id;mName = pkg_name;mPid = pkg_id;mVersion = "0.0.0000";mExtension = v_ext;mAlias = alias;mVcsTag = pkg_vcs_tag;// Remove the package suffix from package_version to create the fixed version numbermFixedVersion = pkg_version;mFixedVersion = mFixedVersion.substring(0, mFixedVersion.length() - mExtension.length());// a ripple_field of 'L' indicates this package has limited version numberingif (change_type == 'M') {mChangeType.setMajor(ripple_field == 'L' ? true : false);} else if (change_type == 'N') {mChangeType.setMinor(ripple_field == 'L' ? true : false);} else if (change_type == 'P') {mChangeType.setPatch(ripple_field == 'L' ? true : false);} else if (change_type == 'F') {mChangeType.setFixed();} else {mChangeType.setUnknown();}}/*** Constructor* Used to create existing packages, in these packages the ripple_field is significant** @param pkg_id Package Name Identifier* @param pv_id Package Version Id* @param pkg_name Package Name* @param pkg_version Package Version* @param v_ext Package Suffix* @param alias Package Alias* @param pkg_vcs_tag Vcs Tag* @param ripple_field Ripple Field*/public Package(int pkg_id,int pv_id, String pkg_name, String pkg_version, String v_ext, String alias, String pkg_vcs_tag, char ripple_field){mLogger.debug("Package 2: pv_id " + pv_id + " pkg_name " + pkg_name + " pkg_version " + pkg_version + " v_ext "+ v_ext + " alias " + alias + " pkg_vcs_tag " + pkg_vcs_tag + " ripple_field " + ripple_field);mId = pv_id;mName = pkg_name;mPid = pkg_id;mVersion = pkg_version;int endindex = mVersion.length() - v_ext.length();if (endindex > 0){mVersion = mVersion.substring(0, endindex);}mExtension = v_ext;mAlias = alias;mVcsTag = pkg_vcs_tag;// setBuild is the defaultif (ripple_field == 'M') {mRippleField.setMajor();} else if (ripple_field == 'm') {mRippleField.setMinor();} else if (ripple_field == 'p') {mRippleField.setPatch();} else if (ripple_field == 'L') {mRippleField.setLimit();}}/*** constructor*/Package(){mLogger.debug("Package 3");mId = 0;mName = "null";mExtension = "null";mAlias = "null";mVcsTag = "null";}/*** Constructor for unit test purposes* Will invoke applyPV and save the results for the UTF framework*/public Package(ReleaseManager rm, String version, int majorLimit, int minorLimit, int patchLimit, int buildNumberLimit){mId = -1;mRippleField.setLimit();mVersion = version;mMajorLimit = majorLimit;mMinorLimit = minorLimit;mPatchLimit = patchLimit;mBuildLimit = buildNumberLimit;if (version.endsWith(".cots")){mExtension = ".cots";mVersion = version.substring(0, version.length() - 5);mChangeType.setMajor(false);mChangeType.setMinor(false);mChangeType.setPatch(true);mRippleField.setBuild();}try{mId = applyPV(rm);} catch (Exception e){}}/*** Constructor for unit test purposes* Performs a partial copy of a package - sufficient for test purposes* @param base - Base package* @param newPvId - New pvid of the package*/@SuppressWarnings("unchecked")public Package(int newPvId, Package base) {mId = newPvId;mPid = base.mPid;mName = base.mName;mExtension = base.mExtension;mVersion = base.mVersion;mAlias = base.mAlias;mVcsTag = base.mVcsTag;mFixedVersion = base.mFixedVersion;mChangeType = base.mChangeType;mRippleField = base.mRippleField;mBuildTime = base.mBuildTime;mHasAutomatedUnitTests = base.mHasAutomatedUnitTests;mSeqId = base.mSeqId;mBuildFailureEmailCollection = (Vector<String>) base.mBuildFailureEmailCollection.clone();mPackageDependencyCollection = (Vector<Package>) base.mPackageDependencyCollection.clone();mDependencyIDCollection = (Vector<Integer>) base.mDependencyIDCollection.clone();mDependencyCollection = (Vector<String>) base.mDependencyCollection.clone();mBuildStandardCollection = (Vector<BuildStandard>) base.mBuildStandardCollection.clone();}/*** accessor for unit test purposes*/public int getId(){return mId;}/*** accessor for unit test purposes*/public String getVersion(){return mVersion;}/*** accessor for unit test purposes*/public String getNextVersion(){return mNextVersion;}/*** returns true if mBuildStandardCollection is not empty*/boolean isReproducible(){mLogger.debug("isReproducible on Package {}", mName);boolean retVal = ! mBuildStandardCollection.isEmpty();mLogger.info("isReproducible returned {}", retVal);return retVal;}/*** returns true if at least one of its BuildStandards has mGeneric true*/boolean isGeneric(){mLogger.debug("isGeneric on Package {}", mName);boolean retVal = false;for (Iterator<BuildStandard> it = mBuildStandardCollection.iterator(); it.hasNext();){BuildStandard buildStandard = it.next();if (buildStandard.isGeneric()){retVal = true;break;}}mLogger.info("isGeneric returned {}", retVal);return retVal;}/*** Returns true if at least one of the packages build standards can be* built in the named machine class.** Used to determine if the package can be built with the current buildset* by iteration over all machine classes in the buildset.*/boolean canBeBuildby(String machineClass){mLogger.debug("canBeBuildby on Package " + mName);boolean retVal = false;for (Iterator<BuildStandard> it = mBuildStandardCollection.iterator(); it.hasNext();){BuildStandard buildStandard = it.next();if (buildStandard.isGeneric()){retVal = true;break;}if (buildStandard.mMachClass.equals(machineClass)){retVal = true;break;}}mLogger.info("canBeBuildby returned " + retVal);return retVal;}/*** Compare the build standards of two packages* Used only in escrow mode* Used to compare a package and its dependents (one by one)** Returns true if the parent (this) package can be built in the same escrow* build iteration as the dependent package.** This is a complex decision and has a few built in assumptions.** If the dependent package is 'generic' then the parent package can be built** If the parent package is generic then is can only be built if the dependent is* also generic. Otherwise we must assume that the parent package is an agregator* package and requires artifacts from the dependent package built by all of its* required build machines.** If both packages are not generic, then if the build standards of the parent and the* dependent are identical then** If we have one build standard, we can build the parent in this iteration as* the dependent package has been completely built.** If we have more than one build standard ( but they are identical ) then we* ASSUME that we can build the parent in this iteration. The assumption is that* there is no mixing between build machines. ie: Windows consumer users Windows* artifacts and the Linux consumer uses Linux artifacts ...** If both packages are not generic and the build standards are not identical, then* things get hard. The safest solution is to assume the parent cannot be built in this* iteration. This is not a bad assumption.**/boolean haveSameBuildStandards(Package d){HashMap<String, Boolean> standardSet = new HashMap<String, Boolean>();boolean isGeneric = false;boolean isGeneric_d = false;boolean isIdentical = true;// Scan the build standards of the parent package and create a hash map for each machine// class required by the parent. Also determine if the parent is generic.for (Iterator<BuildStandard> it = mBuildStandardCollection.iterator(); it.hasNext();){BuildStandard bs = it.next();standardSet.put(bs.mMachClass, false);if (bs.isGeneric()){isGeneric = true;break;}}// Scan the build standards in the dependent package and remove items from the map// If it was not in the map then the dependent package builds for platforms that the// parent package does not - thus the build standards cannot be identical// Also determine if the dependent is generic.for (Iterator<BuildStandard> it = d.mBuildStandardCollection.iterator(); it.hasNext();){BuildStandard bs = it.next();if (bs.isGeneric()){isGeneric_d = true;break;}Boolean value = standardSet.remove(bs.mMachClass);if (value == null){isIdentical = false;break;}}//// If there are any items left in the map then the parent package builds on machines// that the dependent does not. The two are not identical.if (!standardSet.isEmpty()){isIdentical = false;}// If dependent is generic, then it will be build on the first platform in this iteration// All is goodif( isGeneric_d)return true;// If I am generic and the dependent is generic, then I can be built at this time//// Will not reach here as we have already said that if dependenbt is generic, then all is good// if (isGeneric_d && isGeneric)// {// return true;// }// If I am generic then I must wait for ALL dependent to be built on all platforms// Thus I can't be built this round//if (isGeneric)return false;//// If the two sets of build standards don't have generic, BUT are identical// the we can assume that there is no cross breading. and that windows bits build from windows bits// and solaris bits build from solaris//if (isIdentical)return true;// The two sets of build standards are not an exact match// Cheap solution: Assume that I can't be built this round// Possible solution: If I am a subset of the dependent - then I can be built// If I am a superset of the dependent - then ???return false;}/*** Applies the required version number change. Will calculate mNextVersion* while not changing mVersion.** @param releaseManager Release Manager instance to work against** @return 0 : success* 1 : cannot work with non standard versioning* 2 : ripple field limitations prevent a ripple build* 3 : Invalid Change Type* @exception Exception*/int applyPV(ReleaseManager releaseManager) throws Exception{String logInfo = "applyPV," + mName;mLogger.debug("applyPV on Package " + mName);//// This method used to actually perform the version change// Now it just calculates the potential value// Must not alter mVersion as it will be used if the package is not selected to be built,// but some of the code assumes that it can be.//String originalVersion = mVersion;//// Four scenarios, only applyPV for 3 of them// mDirectlyPlanned mIndirectlyPlanned mArchivalExistence mForcedRipple// Action// WIP/test build exists: true true don't care don't care applyPV// Package version is out of date: false true true don't care applyPV// Forced ripple: false true don't care > 0 applyPV// Package version does not exist: false true false = 0 do not applyPV//if (!mDirectlyPlanned && mIndirectlyPlanned && !mArchivalExistence && mForcedRippleInstruction == 0){// the package has an mIndirectlyPlanned flag set true in daemon// mode because the package does not exist in an archive// do not apply a different package versionmLogger.info("applyPV. Rebuild Package {}", mName);mLogger.info("applyPv returned 0");return 0;}// override - no longer doing a rebuild - version number change from this point onif (mTestBuildInstruction == 0){mRequiresSourceControlInteraction = true;}// Force test builds to use a sensible version numberif (mTestBuildInstruction > 0 ){mChangeType.resetData(); // Resolve conflict via build numbersmRippleField.setBuild();mVersion = "99.99.98999"; // Such that rippling build number will goto 99.99.99000}//// Detect invalid change type// Flagged when package instance is created//if (mChangeType.mUnknown){mLogger.info("Package Version specified on Package {} New Version: {}", mName, mVersion);mLogger.info("applyPv returned 3");return 3;}// If we are not calculating the new package version because the user// has fixed the version of the package. We are given the new package version.if (mChangeType.mFixed){// mVersion is already setupmNextVersion = mFixedVersion;mLogger.info("Package Version specified on Package {} New Version: {}", mName, mVersion);mLogger.info("applyPv returned 0");return 0;}// We need to calculate the new version number//MutableInt major = new MutableInt(0);MutableInt minor = new MutableInt(0);MutableInt patch = new MutableInt(1000);// Planned packages have a previous version number to be used as the bases for the// calculation. Ripples won't.//if (mPrevVersion != null){mVersion = mPrevVersion;}String[] field = mVersion.split("\\D");String nonStandardCotsVersion = "";logInfo += ", Prev:" + mVersion;if (field.length == 3){major.value = Integer.parseInt(field[0]);minor.value = Integer.parseInt(field[1]);patch.value = Integer.parseInt(field[2]);}else{//// Can ripple a .cots package under very controlled conditions// Its ends with a .patchBuild field// Package is marked as ripple via build number// Change type of Major and Minor are not allowed//if (!mChangeType.mMajor && !mChangeType.mMinor && mRippleField.mBuild && mExtension.compareTo(".cots") == 0 && field.length > 0){// allow and work with (ripple build) versions a.b.c.d....xxxx// where xxxx.length > 3String patchStr = field[field.length - 1];int patchLen = patchStr.length();// check patchStr is the last (at least 4) digitsif (patchLen > 3&& mVersion.substring(mVersion.length() - patchLen, mVersion.length()).compareTo(patchStr) == 0){patch.value = Integer.parseInt(patchStr);nonStandardCotsVersion = mVersion.substring(0, mVersion.length() - patchLen);}}if (nonStandardCotsVersion.length() == 0){// cannot work with non standard versioningmLogger.error("applyPV cannot work with non standard versioning");mLogger.info("applyPv returned 1");return 1;}}if (nonStandardCotsVersion.length() == 0 && patch.value < 1000 && field[2].substring(0, 1).compareTo("0") != 0){mLogger.info("applyPV accomodate old style Version of the form 1.0.1");patch.value = patch.value * 1000;}// mChangeType overrides mRippleFielddo{if (mChangeType.mMajor){logInfo += ",CT Major";if (!incrementFieldsAccordingToLimits(4, major, minor, patch)){mLogger.info("applyPv returned 2");mVersion = originalVersion;return 2;}} else if (mChangeType.mMinor){logInfo += ",CT Minor";if (!incrementFieldsAccordingToLimits(3, major, minor, patch)){mLogger.info("applyPv returned 2");mVersion = originalVersion;return 2;}} else if (mChangeType.mPatch){logInfo += ",CT Patch";if (!incrementFieldsAccordingToLimits(2, major, minor, patch)){mLogger.info("applyPv returned 2");mVersion = originalVersion;return 2;}} else{if (mRippleField.mMajor){logInfo += ",R Major";major.value++;mLogger.info("applyPV mRippleField.mMajor {}", major.value);minor.value = 0;patch.value = 0;} else if (mRippleField.mMinor){logInfo += ",R Minor";minor.value++;mLogger.info("applyPV mRippleField.mMinor {}", minor.value);patch.value = 0;} else if (mRippleField.mPatch){logInfo += ",R Patch";patch.value = ((patch.value / 1000) + 1) * 1000;mLogger.info("applyPV mRippleField.mPatch {}", patch.value);} else if (mRippleField.mBuild){logInfo += ", R Build";patch.value++;mLogger.info("applyPV mRippleField.mBuild {}", patch.value);} else{if (!incrementFieldsAccordingToLimits(1, major, minor, patch)){mLogger.info("applyPv returned 2");mVersion = originalVersion;return 2;}}}if (nonStandardCotsVersion.length() == 0){mVersion = String.valueOf(major.value) + "." + String.valueOf(minor.value) + ".";} else{mVersion = nonStandardCotsVersion;}if (patch.value < 10){mVersion += "000";} else if (patch.value < 100){mVersion += "00";} else if (patch.value < 1000){mVersion += "0";}mVersion += String.valueOf(patch.value);} while (exists(releaseManager));logInfo += ", Next Version:" + mVersion;mLogger.error(logInfo);mLogger.info("applyPv returned 0");mNextVersion = mVersion;mVersion = originalVersion;return 0;}/*** increments fields according to mRippleField.mLimit if necessary will* apply it to the field passed as follows 1 = build 2 = patch 3 = minor* other = major returns true on success false on ripple field limitations* prevent a ripple build*/private boolean incrementFieldsAccordingToLimits(int field, MutableInt major, MutableInt minor, MutableInt patch){boolean retVal = true;if (!mChangeType.mLimit && !mRippleField.mLimit){// simple case// no need to take field limits into considerationswitch (field){case 1:// unreachable// the only scenario involving build number manipulation// involves the mRippleField.mLimit being setretVal = false;break;case 2:do{patch.value++;} while ((patch.value / 1000) * 1000 != patch.value);mLogger.info("incrementFieldsAccordingToLimits patch " + patch.value);break;case 3:minor.value++;mLogger.info("incrementFieldsAccordingToLimits minor " + minor.value);patch.value = 0;break;default:major.value++;mLogger.info("incrementFieldsAccordingToLimits major " + major.value);minor.value = 0;patch.value = 0;}} else{// take field limits into considerationboolean changeOccurred = false;boolean incrementField = true;switch (field){case 1:if (mBuildLimit != 0){// increment or reset the patch build numberint buildNumber = patch.value - (patch.value / 1000) * 1000;if (buildNumber < mBuildLimit){// can increment the patch build numberpatch.value++;mLogger.info("incrementFieldsAccordingToLimits mRippleField.mLimit build number " + patch.value);changeOccurred = true;incrementField = false;} else{if (mPatchLimit == 0){// reset the patch number and patch build numberpatch.value = 0;}}}// no break by designcase 2:if (mPatchLimit != 0 && incrementField){// increment or reset the patch numberif ((patch.value / 1000) < mPatchLimit){do{patch.value++;} while ((patch.value / 1000) * 1000 != patch.value);mLogger.info("incrementFieldsAccordingToLimits mRippleField.mLimit patch " + patch.value);changeOccurred = true;incrementField = false;} else{// reset the patch number and patch build numberpatch.value = 0;}}// no break by designcase 3:if (mMinorLimit != 0 && incrementField){// increment or reset the minor numberif (minor.value < mMinorLimit){minor.value++;patch.value = 0;mLogger.info("incrementFieldsAccordingToLimits mRippleField.mLimit minor " + minor.value);changeOccurred = true;incrementField = false;} else{// reset the minor numberminor.value = 0;}}// no break by designdefault:if (mMajorLimit != 0 && incrementField){// increment or reset the major numberif (major.value < mMajorLimit){// increment the major numberchangeOccurred = true;major.value++;minor.value = 0;patch.value = 0;mLogger.info("incrementFieldsAccordingToLimits mRippleField.mLimit major " + major.value);}}}if (!changeOccurred){// unable to increment a field due to field limitationsmLogger.error("incrementFieldsAccordingToLimits ripple field limitations prevent a ripple build");mLogger.info("incrementFieldsAccordingToLimits returned false");retVal = false;}}return retVal;}/*** Check if a specified Version of the package exists in dpkg_archive or the Release Manager Database** @param releaseManager Release Manager Instance** @return True if the Package Version exists within the Release Manager Database* @exception Exception*/private boolean exists(ReleaseManager releaseManager) throws Exception{mLogger.debug("exists on Package " + mName + " version " + mVersion + " extension " + mExtension);boolean retVal = false;if (!releaseManager.mUseDatabase){mLogger.info("exists !releaseManager.mUseDatabase");}else{// Check Package ArchiveretVal = existsInDpkgArchive();if (!retVal){// Check Release Manager DatabaseretVal = releaseManager.queryPackageVersions(mPid, mVersion + mExtension);}}mLogger.info("exists returned " + retVal);return retVal;}/*** Check to see if a package exists in dpkg_archive** @return true if the version exists in dpkg_archive* @exception Exception Thrown if dpkg_archive does not exist. The 'cause' of 'mRecoverable' is special and* will be trapped later to determine if this is a recoverable exception.*/boolean existsInDpkgArchive() throws Exception{mLogger.debug("existsInDpkgArchive on " + mName);boolean retVal = false;String name = utilities.catDir(mName, mVersion + mExtension );// If a replica exists, then check it first// If we are configured with a replica its because access to the main archive is slow// and we want this check to be fast//if (mGbeDpkgReplica != null && mGbeDpkgReplica.length() > 0){File dpkg = new File(mGbeDpkgReplica);if (!dpkg.exists()){mLogger.error("existsInDpkgArchive. mGbeDpkgReplica not accessable. " + mRecoverable);throw new Exception(mRecoverable);}if( utilities.freshFileExists(utilities.catDir(mGbeDpkgReplica, name) ) ){mLogger.info("existsInDpkgArchive mGbeDpkgReplica");retVal = true;}}// Check (possibly remote) dpkg_archive for files existence if it was not found locally//if ( !retVal ){// If the package archive does not exist at the moment, then we have a network issue// This is a recoverable errorFile dpkg = new File(mGbeDpkg);if (!dpkg.exists()){mLogger.error("existsInDpkgArchive. mGbeDpkg not accessable. " + mRecoverable);throw new Exception(mRecoverable);}if( utilities.freshFileExists(utilities.catDir(mGbeDpkg, name) ) ){mLogger.info("existsInDpkgArchive mGbeDpkg");retVal = true;}}mArchivalExistence = retVal;mLogger.info("existsInDpkgArchive returned " + retVal);return retVal;}/*** returns true if the required package archives (dpkg_archive) exist* attempt to recover from their transient loss*/public static boolean recover(){mLogger.debug("recover");boolean retVal = false;String Release = mGbeDpkg;if (Release != null){if ( utilities.freshFileExists(mGbeDpkg) ){retVal = true;mLogger.error("recover: dpkg_archive access has been restored");}}mLogger.info("recover returned " + retVal);return retVal;}/*** Returns a data structure of unique email addresses* Uses the Global Email Collection and the packages own failure email collection*/private LinkedHashSet<String>buildEmailList(RippleEngine rippleEngine){// Create a single list of email targets ensuring only one instance of each email address// ie: Remove duplicates, null and empty stringsLinkedHashSet<String> hs = new LinkedHashSet<String>();// Global and Project Wide emailsfor (Iterator<String> it = rippleEngine.mMailGlobalCollection.iterator(); it.hasNext();){String item = it.next();if (item != null && item.length() > 0){hs.add(item);}}// Package specific collectionfor (Iterator<String> it = mBuildFailureEmailCollection.iterator(); it.hasNext();){String item = it.next();if (item != null && item.length() > 0){hs.add(item);}}return hs;}/*** Add email information in a form suitable for creating an Ant file* @param rippleEngine - Ripple Engine Instance* @param xml - An XmlBuilder element to extend*/void emailInfo(RippleEngine rippleEngine, XmlBuilder xml){// Create a single list of email targets ensuring only one instance of each email address// ie: Remove duplicatesLinkedHashSet<String> hs = buildEmailList(rippleEngine);for (Iterator<String> it = hs.iterator(); it.hasNext();){String email = it.next();XmlBuilder entry = xml.addNewElement("owner");entry.addAttribute("email", email);}}/*** Returns email information in a form suitable for direct use* @param rippleEngine Current Release Manager context* @return A comma separated list of user names. May return a 'null' String*/String emailInfoNonAntTask(RippleEngine rippleEngine){// Create a single list of email targets ensuring only one instance of each email address// ie: Remove duplicatesLinkedHashSet<String> hs = buildEmailList(rippleEngine);String retVal = null;for (Iterator<String> it = hs.iterator(); it.hasNext();){String email = it.next();if (retVal == null){retVal = new String();} else{retVal += ",";}retVal += email;}return retVal;}/*** Adds email to mBuildFailureEmailCollection.* Do not worry about multiple entries. These will be handled when the data is extracted* @param email - Email address. Null is allowed and will not be added*/public void addEmail(String email){if (email != null){mBuildFailureEmailCollection.add(email);}}/*** End of Processing for a Test Build* - sends email notification* - marks the instruction complete in the database*/public void completeTestBuild(RippleEngine rippleEngine, boolean success) throws SQLException, Exception{mLogger.debug("completeTestBuild");if (mTestBuildInstruction == 0){mLogger.info("completeTestBuild. Not Build Instruction");return;}// Email SubjectString subject = (success == true ? "TEST BUILD COMPLETED SUCCESSFULLY" : "TEST BUILD FAILED")+ " on package "+ mAlias;// Email BodyString mailBody = "";if ( success != true){mailBody += "Test build issues are identified in preceding build failure email.<p>";}mailBody += "Release: " + rippleEngine.mBaselineName + "<br>"+ "Package: " + mAlias + "<br>"+ "Rm Ref: " + CreateUrls.generateRmUrl(rippleEngine.getRtagId(), mId) + "<br>"+ "VcsTag: " + mVcsTag + "<br>"+ "Build dependencies:<br>";String indentString = " ";for (Iterator<Package> it3 = mPackageDependencyCollection.iterator(); it3.hasNext();){Package depend = it3.next();String dependsExtension = depend.mExtension;String dependsVersion = depend.mVersion;if (dependsExtension.length() > 0){dependsVersion += dependsExtension;}mailBody += indentString + RippleEngine.quoteString(depend.mName, dependsVersion) + "<br>";}mailBody += "<br>Build standards:<br>";for (Iterator<BuildStandard> it = mBuildStandardCollection.iterator(); it.hasNext();){BuildStandard bs = it.next();String bsText = bs.getBuildStandardText();if (bsText.length() > 0){mailBody += indentString + bsText + "<br>";}}mailBody += "<p><hr>";try{String target = emailInfoNonAntTask(rippleEngine);mLogger.error("completeTestBuildEmail Server: " + rippleEngine.getMailServer());mLogger.error("completeTestBuildEmail Sender: " + rippleEngine.getMailSender());mLogger.error("completeTestBuildEmail Target: " + target);Smtpsend.send(rippleEngine.getMailServer(), // mailServerrippleEngine.getMailSender(), // sourcetarget, // targetrippleEngine.getMailSender(), // ccnull, // bccsubject, // subjectmailBody, // bodynull // attachment);} catch (Exception e){mLogger.warn("Email Failure: completeTestBuild:" + e.getMessage());}// Update the Release Manager DatabaserippleEngine.mReleaseManager.markDaemonInstCompleted(mTestBuildInstruction);mLogger.error("completeTest. Returning");}/*** Returns true if the package is a part of a circular dependency** If the package depends on a package with a circular dependency then the function* will return false.*/public boolean hasCircularDependency(RippleEngine ripEng){mLogger.debug("hasCircularDependency: {}", mAlias);boolean retVal = detectCircularDependency(mAlias, ripEng, null);mLogger.info("hasCircularDependency returned {} ", retVal);return retVal;}/*** Returns true is a part of a circular dependency* Will examine all the packages sub dependencies and mark those that do have a* circular dependency.** This process works by descending the dependency tree and dropping a bread crumb* If the bread crumb is seen during the decent, then a circle has been detected and* the package (with the bread crumb) will be marked as having a circular dependency** Assumes that the caller will walk ALL packages and flag those with a circular* dependence AND those that depend on that package.**/private boolean detectCircularDependency(String alias, RippleEngine ripEng, Package parent){mLogger.debug("detectCircularDependency");boolean retVal = false;// if this package has yet to be checked for circular dependencyif (!mCheckedCircularDependency){// Will be set as we drill down through dependencies// If we see this marker (bread crumb) then we have a loopif (mBreadCrumb != 0){mBreadCrumb = 3;mHasCircularDependency = true;if(parent != null && parent.mBreadCrumb != 3 ){parent.mBreadCrumb = 2;}}else{// Mark this package as potentially having a circular dependency// Will now drill down and see if we hit a markermBreadCrumb = 1;// Recurse down the dependencies and sub dependenciesfor (Iterator<String> it2 = mDependencyCollection.iterator(); it2.hasNext();){String dependencyAlias = it2.next();Package dependency = ripEng.findPackage(dependencyAlias);dependency.detectCircularDependency(alias, ripEng, this);}if (mBreadCrumb == 2){mHasCircularDependency = true;if(parent != null && parent.mBreadCrumb != 3 ){parent.mBreadCrumb = 2;}}mBreadCrumb = 0;}// Flag package as having been examinedmCheckedCircularDependency = true;}// return the persisted circular dependency outcomeretVal = mHasCircularDependency;mLogger.info("detectCircularDependency 2 returned {}", retVal);return retVal;}/** Set the sequence of all packages in the list* Used so that the Unit Tests function can preserve the basic ordering of the list*/public static void setSequence(ArrayList<Package> al){int seq = 1;for (Iterator<Package> it = al.iterator(); it.hasNext(); ){Package p = it.next();p.mSeqId = seq++;}}/** Reset the processed flag on a collection of packages**/public static void resetProcessed (ArrayList<Package> al){for (Iterator<Package> it = al.iterator(); it.hasNext(); ){Package p = it.next();p.mIsProcessed = false;}}/** Reset the rippleOrder flag on a collection of packages**/public static void resetRippleOrder (ArrayList<Package> al){for (Iterator<Package> it = al.iterator(); it.hasNext(); ){Package p = it.next();p.mRippleOrder = 0;}}/** Comparator for sorting package collections by mSeqId* Used to preserve order for unit testing*/public static final Comparator<Package> SeqComparator = new Comparator<Package>() {/*** Returns -ve: p1 is less than p2* 0: p1 = p2* +ve: p1 > p2*/public int compare (Package p1, Package p2) {return p1.mSeqId - p2.mSeqId;}};/** Comparator for sorting package collections by mRippleTimme and mRippleOrder* Preserve rippleOrder**/public static final Comparator<Package> PlanComparator = new Comparator<Package>() {/*** Returns -ve: p1 is less than p2* 0: p1 = p2* +ve: p1 > p2*/public int compare (Package p1, Package p2) {if (p1.mRippleOrder == p2.mRippleOrder){return p1.mRippleTime - p2.mRippleTime;}return p1.mRippleOrder - p2.mRippleOrder;}};/** Comparator for sorting package collections by mRippleTimme (only)**/public static final Comparator<Package> RippleTimeComparator = new Comparator<Package>() {/*** Returns -ve: p1 is less than p2* 0: p1 = p2* +ve: p1 > p2*/public int compare (Package p1, Package p2) {return p1.mRippleTime - p2.mRippleTime;}};/*** entity class supporting the ERG version numbering standard:* <major>.<minor>.<patch/build> patch/build is at least a 4 digit number* whose last 3 digits represent the build*/public class VersionNumberingStandard{/*** in terms of the mChangeType Package field, when true indicates the* contract of the package has changed in a non backwardly compatible* manner in terms of the mRippleField Package field, when true indicates* the major version number will be incremented** @attribute*/private boolean mMajor = false;/*** in terms of the mChangeType Package field, when true indicates the* contract of the package has changed in a backwardly compatible manner* in terms of the mRippleField Package field, when true indicates the* minor version number will be incremented** @attribute*/private boolean mMinor = false;/*** in terms of the mChangeType Package field, when true indicates the* contract of the package has not changed, but the package has changed* internally in terms of the mRippleField Package field, when true* indicates the minor version number will be incremented** @attribute*/private boolean mPatch = false;/*** in terms of the mChangeType Package field, when true indicates the* package has not changed, its dependencies potentially have in terms* of the mRippleField Package field, when true indicates the build* number will be incremented** @attribute*/private boolean mBuild = true;/*** in terms of the mChangeType Package field, when true indicates the* major, minor, and patch number will be incremented according to field* limits in terms of the mRippleField Package field, when true indicates* the major, minor, patch and build number will be incremented* according to field limits** @attribute*/private boolean mLimit = false;/*** in terms of the mChangeType Package field, when true indicates the* package version number will not be rippled. The user will have fixed* the version number. This is only application to WIP packages** @attribute*/private boolean mFixed = false;/*** in terms of the mChangeType Package field, when true indicates the* method of rippling a package version number is not known.** @attribute*/private boolean mUnknown = false;/*** constructor*/private VersionNumberingStandard(){mLogger.debug("VersionNumberingStandard");}/*** Reset all values to a known state**/void resetData(){mBuild = false;mMajor = false;mMinor = false;mPatch = false;mLimit = false;mFixed = false;mUnknown = false;}/*** sets mBuild true, mMajor false, mMinor false, mPatch false, mLimit* false*/void setBuild(){mLogger.debug("setBuild");resetData();mBuild = true;}/*** sets mBuild false, mMajor true, mMinor false, mPatch false, mLimit* false*/void setMajor(){mLogger.debug("setMajor");resetData();mMajor = true;}/*** sets mBuild false, mMajor true, mMinor false, mPatch false, mLimit* limit*/void setMajor(boolean limit){mLogger.debug("setMajor " + limit);resetData();mMajor = true;mLimit = limit;}/*** sets mBuild false, mMajor false, mMinor true, mPatch false, mLimit* false*/void setMinor(){mLogger.debug("setMinor");resetData();mMinor = true;}/*** sets mBuild false, mMajor false, mMinor true, mPatch false, mLimit* limit*/void setMinor(boolean limit){mLogger.debug("setMinor " + limit);resetData();mMinor = true;mLimit = limit;}/*** sets mBuild false, mMajor false, mMinor false, mPatch true, mLimit* false*/void setPatch(){mLogger.debug("setPatch");resetData();mPatch = true;}/*** sets mBuild false, mMajor false, mMinor false, mPatch true, mLimit* limit*/void setPatch(boolean limit){mLogger.debug("setPatch");resetData();mPatch = true;mLimit = limit;}/*** sets mBuild false, mMajor false, mMinor false, mPatch false, mLimit* true*/void setLimit(){mLogger.debug("setPatch");resetData();mLimit = true;}/*** sets parameters to indicate that the change type is Fixed. The* version number is set by the user and a ripple will not be calculated*/void setFixed(){mLogger.debug("setFixed");resetData();mFixed = true;}/*** Sets parameters to indicate that the change type is not known**/void setUnknown(){resetData();mUnknown = true;}}/*** Add a package dependency* UTF Use* @param p - The package to add as a dependent* @return - The current package to allow chaining of calls*/public Package addDependency(Package p) {mDependencyCollection.add(p.mAlias);mDependencyIDCollection.add(p.mId);return this;}/** Clear the packages list of dependencies* UTF use* @return - The current package to allow chaining of calls*/public Package resetDependencies() {mDependencyCollection.clear();mDependencyIDCollection.clear();return this;}}