view scripts/release.groovy @ 219:efa545c6dd65

DEP-11: implement release process as gradle rule
author smith@nwoca.org
date Fri, 01 Jul 2016 20:56:04 +0100
parents 49a220a1bde0
children
line wrap: on
line source
#!groovy
import groovy.transform.Sortable
import groovy.transform.ToString
import groovy.transform.TupleConstructor

/**
 This script implements the SSDT branching strategy based on hg flow
 and dependency resolution locking.

 The intention is to automate of creation of correctly configured release branches.
 The script tries to do the right thing based on standard SSDT project structures,
 but it is the user's responsibility to ensure it's correct.

 The script does NOT "hg push --new-branch".   That step is left for you
 if the branch was created correctly.

*/

def branch = new BranchInfo()

println "Project:\n"

println "\trepo\t${("hg path".execute().text.split('=') ?: ['', ''])[1].trim()}"
println "\tbranch\t$branch"
println "\tversion\t$branch.version"

println "-" * 40
println ""

if (args.size() < 1) {
	println """	
	usage:  release.groovy {major|minor|patch|n.n.n}\n
	  e.g:  release.groovy minor

	If "major", "minor" or "patch" is specified, then the release version is
	calculated based on the current branch.  Otherwise specify a specific version.

	If release ends in ".0", then will create 'release' stream, otherwise 'hotfix'.
	For hotfix, current working branch should be the release branch being hotfix'ed.
	
	Recommend that this script be executed in a fresh clone of the repo.
	
	** Any uncommitted changes in the working directory will be committed with
	   the initial setting of the version. These are assumed to be
	   'latest.integration' changes.
"""	

	System.exit(0)
}

def releaseVersion

if ( args[0] == 'major') {
	releaseVersion = branch.version.nextMajor()
} else if ( args[0] == 'minor') {
	releaseVersion = branch.version.nextMinor()
} else if ( args[0] == 'patch') {
	releaseVersion = branch.version.nextPatch()
} else {
	releaseVersion = new Version(*args[0].split('\\.')*.toInteger())
}

def hotfix = releaseVersion.patch > 0

def stream = hotfix ? 'hotfix' : 'release'

println "Preparing to create $stream branch for version $releaseVersion"
println()

checkForSnapshots()

println "hg flow $stream start v${releaseVersion} --dry-run".execute().text

println "Continue? Enter = Yes, ^C to cancel"
System.in.newReader().readLine()

println "hg flow ${stream} start v${releaseVersion} --dirty".execute().text

println "hg update ${stream}/v${releaseVersion}".execute().text

println "Starting dependency lock via gradle... (please wait)"
println "cmd /c gradlew.bat deleteGlobalLock generateGlobalLock saveGlobalLock".execute().text

println 'hg commit -A release.lock -m "lock dynamic dependencies for release"'.execute().text

println "Created $releaseVersion $stream branch with locked dynamic dependencies."
println "   Verify the branch and release.lock file created correctly then push the new branch."
println "   If any problems, then delete repo and clone a fresh copy repository."

def checkForSnapshots() {
		def lines = new File('gradle.properties').readLines() + new File('build.gradle').readLines()
		def snapshots = lines.collect { it.trim() }.findAll{ 
			it.contains('.SNAPSHOT') && !it.startsWith('version=') ||
			it.contains('latest.') && (it.startsWith('compile') || it.startsWith('runtime'))
		}
		if (snapshots) {
			println "project contains SNAPSHOT dependencies: \n\t" + snapshots.join('\n\t')
			System.exit(1)
		}
}


@TupleConstructor
@Sortable
class Version {

	Integer major = 0
	Integer minor = 0
	Integer patch = 0
	Boolean snapshot = false

	Version nextMajor() {
		new Version(major + 1, 0, 0)
	}

	Version nextMinor() {
		if (snapshot) {
			new Version(major, minor , 0)
		} else {
			new Version(major, minor + 1, 0)
		}
	}

	Version nextSnapshot() {
		new Version(major, minor + 1, 0,true)
	}

	Version nextPatch() {
		new Version(major, minor, patch + 1)
	}

	String toString() {
		"${major}.${minor}.${patch}${snapshot ? '.SNAPSHOT' : ''}"
	}

}


@ToString(includes=['name','shortName','buildVersion','imageId','deployName'],includeNames= true)
class BranchInfo {
	def name
	def stream = "none"
	def buildNumber = ""
	def changeset = ""
	def version

	BranchInfo(name = null) {
		this.name = name
		if (!name) {
			this.name = determineName() ?: ''
		}
		this.name = this.name.replace('@', '-')
		determineStream()
		buildNumber = System.getenv('bamboo_buildNumber') ?: ""
		changeset = System.getenv('bamboo_planRepository_revision') ?: ""
	}

	String getDefaultDependencyStatus() {
		return isRelease() ? 'release' : 'integration'
	}

	private boolean isRelease() {
		return stream in ['release', 'hotfix']
	}

	def getShortName() {
		def result = name.contains('/') ? name.split('/')[1] : name
	}

	String getBuildVersion() {
		def v = isRelease() ? shortName - "v": ""
		return v
	}

	def Version getVersion() {
		if (!version) {
			if (isRelease()) {
				version = new Version(*getBuildVersion().split('\\.')*.toInteger(), false)
			} else {
				version = findSnapshotVersion()
			}
		}
		return version
	}

	def getImageId() {
		(buildVersion ?:  shortName.take(25)) + (buildNumber ? "-${buildNumber}" : "-0")
	}

	def getDeployName() {
		(buildVersion ?:  shortName.take(25)).toLowerCase().collectReplacements {
			('a'..'z').contains(it) || ('0'..'9').contains(it) || it == "-" ? null : '-'
		}
	}

	def getHash() {
		generateMD5(name)
	}

	def generateMD5(String s) {
		def digest = java.security.MessageDigest.getInstance("MD5")
		digest.update(s.bytes);
		new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
	}

	private findSnapshotVersion() {
		def versions = "hg branches --closed".execute().text.split('\n').findAll {
			it.startsWith( 'release') || it.startsWith( 'hotfix')
		}.collect {
			it.replaceAll('\\s+',' ').split(' ')[0].split('/')[1] - 'v'
		}.collect {
			new Version(*it.split('\\.')*.toInteger(),true)
		}.sort { v1, v2 -> v2 <=> v1 }

		return versions ?  versions.first().nextSnapshot() : new Version().nextSnapshot()

	}


	def determineName()  {
		try {
			def branch = "hg branch".execute().text.trim()
			def rawParents = 'hg parents'.execute().text
			def parent = rawParents.split('\n').find { it.startsWith("branch") }?.split(":")?.getAt(1)?.trim()
			return parent ?: branch
		} catch (e) {
			['.hg/branch', '../.hg/branch'].findResult {
				new File(it).exists() ? new File(it).text : null
			}
		}
	}

	void determineStream() {
		def flowConfig = new File('.hgflow').exists() ? new File('.hgflow') : new File('../.hgflow')
		if (flowConfig.exists()) {
			def flows = new Properties()
			flows.load(flowConfig.newReader())
			flows.stringPropertyNames().each {
				if (!it.startsWith("[") && name.startsWith(flows.getProperty(it))) {
					stream = it
				}
			}
		}
	}

}