diff --git a/src/alclabs-gradle.gradle b/src/alclabs-gradle.gradle
index 690beb2..644283d 100644
--- a/src/alclabs-gradle.gradle
+++ b/src/alclabs-gradle.gradle
@@ -7,285 +7,333 @@ apply plugin: 'war'
apply plugin: 'groovy'
apply plugin: 'idea'
apply plugin: 'eclipse'
+apply plugin:'base'
-repositories {
- mavenCentral()
-
- // since JQuery & JQueryUI are frequently used by add-ons, include their repo here as a convenience
- ivy {
- url "http://code.jquery.com"
- layout "pattern", {
- artifact "[module]-[revision](.[classifier]).[ext]"
- }
- }
- ivy {
- url "http://code.jquery.com/ui"
- layout "pattern", {
- artifact "[revision]/[module](.[classifier]).[ext]"
- }
- }
+boolean includeJquery()
+{
+ return !project.ext.has( "excludeJquery" ) ||
+ !"true".equals( project.ext.excludeJquery )
+}
+
+if( includeJquery() )
+{
+ repositories {
+ mavenCentral()
+
+ // since JQuery & JQueryUI are frequently used by add-ons, include their repo here as a convenience
+ //ivy {
+ // url "http://code.jquery.com"
+ // layout "pattern", {
+ // artifact "[module]-[revision](.[classifier]).[ext]"
+ //}
+ //}
+ //ivy {
+ // url "http://code.jquery.com/ui"
+ // layout "pattern", {
+ // artifact "[revision]/[module](.[classifier]).[ext]"
+ //}
+ // }
+ }
}
final jsDir = 'js'
configurations {
- js // used to specify javascript dependencies. These are all put in 'jsDir' when the .war is built.
+ js // used to specify javascript dependencies. These are all put in 'jsDir' when the .war is built.
}
project.convention.plugins.addoninfo = new AddOnInfo()
+project.convention.plugins.addoninfo.addExtension('providers', new ProviderExtension())
-final addonInfoFile = new File(buildDir, 'tmp/war/META-INF/addon-info.xml')
+final addonInfoFile = new File(buildDir, 'tmp/war/info.xml')
task generateAddOnInfo {
- afterEvaluate {
- inputs.properties project.convention.plugins.addoninfo.properties
- outputs.file addonInfoFile
- }
- doLast {
- def addoninfo = project.convention.plugins.addoninfo
- def props = addoninfo.properties
- if (props.any { key, value -> value != null }) {
- def text = '\r\n'
- props.each { key, value ->
- if (value) {
- def keyText = addoninfo.propNameToXmlElementName(key)
- text += " <$keyText>$value$keyText>\r\n"
- }
- }
- text += ''
-
- addonInfoFile.parentFile.mkdirs()
- addonInfoFile.text = text
- }
- else
- addonInfoFile.delete()
- }
+ afterEvaluate {
+ inputs.properties project.convention.plugins.addoninfo.properties
+ outputs.file addonInfoFile
+ }
+ doLast {
+ def text = new StringWriter()
+ new groovy.xml.MarkupBuilder(text).extension(version: 1) {
+ project.convention.plugins.addoninfo.addXml(delegate)
+ }
+
+ addonInfoFile.parentFile.mkdirs()
+ addonInfoFile.text = text.toString()
+ }
+}
+
+task addon(description: "Produces the .addon file", type: Zip) {
+ dependsOn generateAddOnInfo, war
+ inputs.files addonInfoFile, 'LICENSE.txt'
+
+ destinationDir = file("$buildDir/libs")
+ baseName = project.name.toLowerCase().replace( " ", "_" ) // lower-case'ify the addon name to simplify usage in a web browser (which is case-sensitive)
+ extension = 'addon'
+ fileMode = 0644
+
+ from(addonInfoFile) // include addon-info.xml in binary
+ from('LICENSE.txt') // include license in binary
+
+ into ('webapp') {
+ with(war)
+ }
+
+ // have the build tell you where the archive was put!
+ doLast {
+ println "Addon created as ${addon.archivePath}"
+ }
}
+build.dependsOn addon
+
war {
- dependsOn generateAddOnInfo
- inputs.files addonInfoFile, 'LICENSE.txt'
-
- // lower-case'ify the war name to simplify usage in a web browser (which is case-sensitive)
- archiveName = war.baseName.toLowerCase() + '.war'
-
- // include addon-info.xml in binary
- from(addonInfoFile) {
- into 'META-INF'
- }
-
- // include license in binary
- from('LICENSE.txt') {
- into 'WEB-INF'
- }
-
- // include javascript libraries
- from(configurations.js) {
- into jsDir
- }
+ enabled = false
+ fileMode = 0644
+
+ // include javascript libraries
+ from (configurations.js) {
+ into jsDir
+ }
}
// not intended to be used directly. This is the default task for the script. This allows
// the user to "double-click" on 'gradlew' and have the GUI launch.
-task showGui(description: 'Starts a graphical user interface for the build') << {
- org.gradle.gradleplugin.userinterface.swing.standalone.BlockingApplication.launchAndBlock();
+task showGui(description: 'Starts a graphical user interface for the build') { doLast {
+ org.gradle.gradleplugin.userinterface.swing.standalone.BlockingApplication.launchAndBlock();
+}
}
// Helper method to get the installation root directory of the server. This method will either
// return whatever is in the 'serverRootDir' property (if it's set) ...
-File getServerDir() {
- if (!project.ext.has("serverRootDir"))
- project.ext.set("serverRootDir", null);
- if (project.ext.serverRootDir == null) {
- Properties props = new Properties()
- File optionsFile = new File('build.options')
- if (optionsFile.exists()) {
- optionsFile.withReader { props.load(it) }
- project.ext.serverRootDir = props.getProperty('serverRootDir')
- }
-
- if (project.ext.serverRootDir == null) {
- File dir = pickServerDir()
- if (dir == null)
- throw new Exception("""serverRootDir property (path to webctrl install directory) not set.
+File determineServerDir() {
+ if (!project.ext.has("serverRootDir"))
+ project.ext.set("serverRootDir", null);
+ if (project.ext.serverRootDir == null) {
+ Properties props = new Properties()
+ File optionsFile = new File('build.options')
+ if (optionsFile.exists()) {
+ optionsFile.withReader { props.load(it) }
+ project.ext.serverRootDir = props.getProperty('serverRootDir')
+ }
+
+ if (project.ext.serverRootDir == null) {
+ File dir = pickServerDir()
+ if (dir == null)
+ throw new Exception("""serverRootDir property (path to webctrl install directory) not set.
Please set the serverRootDir property on the command line (-PserverRootDir=
) or in the userHome/.gradle/gradle.properties file.""")
- project.ext.serverRootDir = dir.absolutePath.replaceAll('\\\\', '/')
- props.setProperty('serverRootDir', project.ext.serverRootDir)
- optionsFile.withWriter { props.store(it, null) }
- }
- }
- return new File(project.ext.serverRootDir)
+ project.ext.serverRootDir = dir.absolutePath.replaceAll('\\\\', '/')
+ props.setProperty('serverRootDir', project.ext.serverRootDir)
+ optionsFile.withWriter { props.store(it, null) }
+ }
+ }
+ return new File(project.ext.serverRootDir)
}
-project.ext.set("getServerDir", {getServerDir()});
+project.ext.set("getServerDir", {determineServerDir()});
// Helper methods to get the directory in which to deploy webapps (add-ons).
-File getDeployLoc() { new File(getServerDir(), 'webserver/webapps/'+war.baseName.toLowerCase()) }
+project.ext.getDeployLoc = { new File(project.ext.getServerDir(), 'addons/'+war.baseName.toLowerCase()) }
// task that deletes the .war (and exploded dir) from the webserver
-task cleanDeploy(description: 'Deletes the war from the webserver', overwrite: true, type:Delete)
-cleanDeploy.doFirst { delete getDeployLoc() }
+//task cleanDeploy(description: 'Deletes the war from the webserver', overwrite: true, type:Delete)
+//cleanDeploy.doFirst { delete project.ext.getDeployLoc() }
// task that builds and deploys the .war to the webserver
-task deploy(description: 'Deploys the war to the webserver', type: Sync, dependsOn:war) {
- doFirst {
- println "Deploying $war.baseName to ${getDeployLoc().canonicalPath}"
- }
- from zipTree(war.archivePath)
- into { getDeployLoc() }
- fileMode = 0644
+task deploy(description: 'Deploys the war to the webserver', dependsOn:[sourceSets.main.runtimeClasspath, generateAddOnInfo]) {
+ doLast {
+
+ project.copy {
+ with addon
+ into { project.ext.getDeployLoc() }
+ fileMode = 0644
+ }
+ }
+}
+deploy.doLast {
+ println "Deployed $war.baseName to ${project.ext.getDeployLoc().canonicalPath}"
}
-// have build tell you where the archive was put!
-build.doLast {
- println "War created as ${war.archivePath}"
+// have war tell you where the archive was put!
+war.doLast {
+ println "War created as ${war.archivePath}"
}
-task wrapper(type: Wrapper) {
- gradleVersion = '1.0-milestone-9'
- jarFile = file('wrapper/gradle-wrapper.jar')
- archiveBase = Wrapper.PathBase.GRADLE_USER_HOME
+// if needed, specify a special repo for the keystore
+if (hasProperty('addon_sign_repo')) {
+ repositories { maven { url addon_sign_repo } }
}
-File pickServerDir() {
- def serverDir = null;
- def builder = new groovy.swing.SwingBuilder()
- builder.registerBeanFactory('folderChooser', com.jidesoft.swing.FolderChooser)
- builder.build {
- frame(id: 'mainframe', title:'Pick Server Root Dir', defaultCloseOperation:JFrame.EXIT_ON_CLOSE, size:[440,440], locationRelativeTo: null, show: true) {
- panel(border: emptyBorder(10)) {
- boxLayout axis: BoxLayout.Y_AXIS
- panel {
- boxLayout axis: BoxLayout.X_AXIS
- label text: 'In order to deploy the add-on to the server (and to run some tests), the build needs to know where the root directory of the server is located.'
+// Allows add-on to request signing. Only works for official add-ons.
+ext.signAddon = {
+ // cannot sign jars with duplicate files, so make sure that doesn't happen
+ addon {
+ duplicatesStrategy = "exclude"
+ }
+
+ // add a task to perform the actual signing
+ def requiredSigningProperties = ['addon_sign_keystore_gav', 'addon_sign_alias', 'addon_sign_password', 'addon_sign_keypass']
+ task signArchive(description: 'Signs the add-on archive', dependsOn: war) << {
+ if (!requiredSigningProperties.every { project.hasProperty(it) })
+ {
+ println "All signing properties not defined, the built add-on will not be signed."
+ println "The required prooperties are:"
+ requiredSigningProperties.each { println " "+it }
}
- rigidArea size: new Dimension(0, 10)
- panel {
- boxLayout axis: BoxLayout.Y_AXIS
- panel {
- boxLayout axis: BoxLayout.X_AXIS
- label text: 'Server root dir:'
- hglue()
- }
- rigidArea size: new Dimension(0, 5)
- panel {
- boxLayout axis: BoxLayout.X_AXIS
- folderChooser id: 'driverDirChooser', currentDirectory: new File('.'), navigationFieldVisible: false, controlButtonsAreShown: false, availableButtons: 0, recentListVisible: false
- hglue()
- }
+ else
+ {
+ def keyDep = dependencies.create(addon_sign_keystore_gav)
+ def keystore = configurations.detachedConfiguration(keyDep).resolve().iterator().next()
+ ant.signjar(jar: addon.archivePath, alias:addon_sign_alias, storepass:addon_sign_password, keystore:keystore.path, keypass:addon_sign_keypass)
}
- rigidArea size: new Dimension(0, 10)
- panel {
- boxLayout axis: BoxLayout.X_AXIS
- hglue()
- button text: 'Accept', actionPerformed: {
- serverDir = builder.driverDirChooser.selectedFolder
- dispose()
- }
- hglue()
- button text: 'Cancel', actionPerformed: {
- dispose()
- }
- hglue()
+ }
+
+ // hook in the signArchive task to occur whenever a build is performed
+ project.afterEvaluate {
+ tasks['build'].dependsOn signArchive
+ }
+
+ // if signing, allow the add-on to specify it's own licensing requirements (including none). The server
+ // will only honor this data if the add-on is signed with the valid private key.
+ project.convention.plugins.addoninfo.addExtension('licenseRequirements', new LicenseRequirementsExtension())
+}
+
+File pickServerDir() {
+ def serverDir = null;
+ def builder = new groovy.swing.SwingBuilder()
+ builder.registerBeanFactory('folderChooser', com.jidesoft.swing.FolderChooser)
+ builder.build {
+ frame(id: 'mainframe', title:'Pick Server Root Dir', defaultCloseOperation:JFrame.EXIT_ON_CLOSE, size:[440,440], locationRelativeTo: null, show: true) {
+ panel(border: emptyBorder(10)) {
+ boxLayout axis: BoxLayout.Y_AXIS
+ panel {
+ boxLayout axis: BoxLayout.X_AXIS
+ label text: 'In order to deploy the add-on to the server (and to run some tests), the build needs to know where the root directory of the server is located.'
+ }
+ rigidArea size: new Dimension(0, 10)
+ panel {
+ boxLayout axis: BoxLayout.Y_AXIS
+ panel {
+ boxLayout axis: BoxLayout.X_AXIS
+ label text: 'Server root dir:'
+ hglue()
+ }
+ rigidArea size: new Dimension(0, 5)
+ panel {
+ boxLayout axis: BoxLayout.X_AXIS
+ folderChooser id: 'driverDirChooser', currentDirectory: new File('.'), navigationFieldVisible: false, controlButtonsAreShown: false, availableButtons: 0, recentListVisible: false
+ hglue()
+ }
+ }
+ rigidArea size: new Dimension(0, 10)
+ panel {
+ boxLayout axis: BoxLayout.X_AXIS
+ hglue()
+ button text: 'Accept', actionPerformed: {
+ serverDir = builder.driverDirChooser.selectedFolder
+ dispose()
+ }
+ hglue()
+ button text: 'Cancel', actionPerformed: {
+ dispose()
+ }
+ hglue()
+ }
+ }
}
- }
}
- }
- // block until the gui is closed
- while (builder.mainframe.isVisible()) {
- Thread.sleep(100)
- }
- if (serverDir == null)
- throw new GradleException("ERROR: Build cannot continue because the root directory of the server was not specified")
- return serverDir
+ // block until the gui is closed
+ while (builder.mainframe.isVisible()) {
+ Thread.sleep(100)
+ }
+ if (serverDir == null)
+ throw new GradleException("ERROR: Build cannot continue because the root directory of the server was not specified")
+ return serverDir
}
def projRef = project
rootProject.idea {
- project {
- //if you want to set specific jdk and language level
- projRef.afterEvaluate {
- jdkName = "${projRef.sourceCompatibility}"
- languageLevel = "${projRef.sourceCompatibility}"
- }
+ project {
+ //if you want to set specific jdk and language level
+ projRef.afterEvaluate {
+ jdkName = "${projRef.sourceCompatibility}"
+ languageLevel = "${projRef.sourceCompatibility}"
+ }
- ipr.withXml {
- def node = it.asNode()
+ ipr.withXml {
+ def node = it.asNode()
- def vcsConfig = node.component.find { it.'@name' == 'VcsDirectoryMappings' }
- vcsConfig.mapping[0].'@vcs' = 'Git'
+ def vcsConfig = node.component.find { it.'@name' == 'VcsDirectoryMappings' }
+ vcsConfig.mapping[0].'@vcs' = 'Git'
- def mgrNode = node.component.find { it.'@name' == 'ArtifactManager' }
- if (!mgrNode)
- mgrNode = node.appendNode('component', [name: 'ArtifactManager'])
+ def mgrNode = node.component.find { it.'@name' == 'ArtifactManager' }
+ if (!mgrNode)
+ mgrNode = node.appendNode('component', [name: 'ArtifactManager'])
- def webNode = mgrNode?.artifact.find { it.'@name' == 'Add-On Web exploded' }
- if (webNode)
- mgrNode.remove(webNode)
+ def webNode = mgrNode?.artifact.find { it.'@name' == 'Add-On Web exploded' }
+ if (webNode)
+ mgrNode.remove(webNode)
- mgrNode.append(new XmlParser().parseText("""
+ mgrNode.append(new XmlParser().parseText("""
- ${pathFactory.relativePath('PROJECT_DIR', getDeployLoc()).relPath}
+ ${pathFactory.relativePath('PROJECT_DIR', new File(rootProject.getDeployLoc(), 'webapp')).relPath}
-
+
"""))
- def libNode = mgrNode.artifact.root.element.find { it.'@name' == 'WEB-INF' }.element.find { it.'@name' == 'lib' }
- def userHome = new File(System.getProperty("user.home"))
- modules[0].resolveDependencies().each { dep ->
- if (dep instanceof org.gradle.plugins.ide.idea.model.ModuleLibrary)
- {
- if (dep.scope == 'COMPILE' || dep.scope == 'RUNTIME')
- dep.classes.each {
- libNode.appendNode('element', [id: 'file-copy', path: pathFactory.resolvePath(userHome, '$USER_HOME$', it.file).relPath])
+ def libNode = mgrNode.artifact.root.element.find { it.'@name' == 'WEB-INF' }.element.find { it.'@name' == 'lib' }
+ def userHome = new File(System.getProperty("user.home"))
+ modules[0].resolveDependencies().each { dep ->
+ if (dep instanceof org.gradle.plugins.ide.idea.model.ModuleLibrary)
+ {
+ if (dep.scope == 'COMPILE' || dep.scope == 'RUNTIME')
+ dep.classes.each {
+ libNode.appendNode('element', [id: 'file-copy', path: pathFactory.resolvePath(userHome, '$USER_HOME$', it.file).relPath])
+ }
+ }
+ }
+ def jsLibs = modules[0].project.configurations.js.resolve()
+ if (!jsLibs.isEmpty())
+ {
+ def jsNode = mgrNode.artifact.root.element.find { it.'@name' == jsDir }
+ if (!jsNode)
+ jsNode = mgrNode.artifact.root[0].appendNode('element', [id: 'directory', name: jsDir])
+
+ jsLibs.each {
+ jsNode.appendNode('element', [id: 'file-copy', path: pathFactory.resolvePath(userHome, '$USER_HOME$', it).relPath])
+ }
}
}
- }
- if (modules[0].project.configurations.findByName('js'))
- {
- def jsLibs = modules[0].project.configurations.js.resolve()
- if (!jsLibs.isEmpty())
- {
- def jsNode = mgrNode.artifact.root.element.find { it.'@name' == jsDir }
- if (!jsNode)
- jsNode = mgrNode.artifact.root[0].appendNode('element', [id: 'directory', name: jsDir])
-
- jsLibs.each {
- jsNode.appendNode('element', [id: 'file-copy', path: pathFactory.resolvePath(userHome, '$USER_HOME$', it).relPath])
- }
- }
- }
}
- }
}
idea {
- module {
- scopes.PROVIDED.plus += [configurations.providedCompile]
- scopes.PROVIDED.plus += [configurations.providedRuntime]
- scopes.COMPILE.minus += [configurations.providedCompile]
- scopes.RUNTIME.minus += [configurations.providedRuntime]
-
- inheritOutputDirs = false
- outputDir = file("$buildDir/classes/main")
- testOutputDir = file("$buildDir/classes/test")
-
- iml.withXml {
- def node = it.asNode()
-
- def mgrNode = node.component.find { it.'@name' == 'FacetManager' }
- if (!mgrNode)
- mgrNode = node.appendNode('component', [name: 'FacetManager'])
- def webNode = mgrNode?.facet.find { it.'@name' == 'Add-On Web' }
- if (webNode)
- mgrNode.remove(webNode)
-
- mgrNode.append(new XmlParser().parseText('''
+ module {
+ scopes.PROVIDED.plus += [configurations.providedCompile]
+ scopes.PROVIDED.plus += [configurations.providedRuntime]
+ scopes.COMPILE.minus += [configurations.providedCompile]
+ scopes.RUNTIME.minus += [configurations.providedRuntime]
+
+ inheritOutputDirs = false
+ outputDir = file("$buildDir/classes/main")
+ testOutputDir = file("$buildDir/classes/test")
+
+ iml.withXml {
+ def node = it.asNode()
+
+ def mgrNode = node.component.find { it.'@name' == 'FacetManager' }
+ if (!mgrNode)
+ mgrNode = node.appendNode('component', [name: 'FacetManager'])
+ def webNode = mgrNode?.facet.find { it.'@name' == 'Add-On Web' }
+ if (webNode)
+ mgrNode.remove(webNode)
+
+ mgrNode.append(new XmlParser().parseText('''
@@ -301,37 +349,158 @@ idea {
'''))
+ }
}
- }
}
buildscript {
- repositories { mavenCentral() }
+
+ if( project.hasProperty( 'defineBuildScriptRepositories' ) )
+ project.ext.defineBuildScriptRepositories( repositories )
+ else
+ repositories { mavenCentral() }
+
dependencies { classpath group: 'com.jidesoft', name: 'jide-oss', version: '2.9.0' }
}
-// Support for addon-info.xml
+// Support for info.xml
class AddOnInfo {
- def String name
- def String description
- def String version
- def String vendor
- def String systemMenuProvider
-
- void info(Closure c) {
- c.setDelegate(this)
- c.setResolveStrategy Closure.DELEGATE_ONLY
- c()
- }
-
- String propNameToXmlElementName(String propName)
- {
- // this converts a camelCase string to dash-separated all lower case string (i.e. camel-case)
- propName.replaceAll('[A-Z]', { '-'+it.toLowerCase() })
- }
-
- Map getProperties() {
- [ name:name, description:description, version:version, vendor:vendor, systemMenuProvider:systemMenuProvider ]
- }
+ def String name
+ def String description
+ def String version
+ def String vendor
+ Map extensions = [:]
+ def supportedApplications = []
+
+ void info(Closure c) {
+ c.setDelegate(this)
+ c.setResolveStrategy Closure.DELEGATE_ONLY
+ c()
+ }
+
+ def addExtension(name, obj) {
+ extensions.put(name, obj)
+ }
+
+ // for backwards compatibility
+ def propertyMissing(String name, value) {
+ if (name == 'systemMenuProvider')
+ extensions.get('providers')?.systemMenu = value
+ else
+ throw new MissingPropertyException(name)
+ }
+
+ def methodMissing(String name, args) {
+ if (args[0] instanceof Closure)
+ {
+ def ext = extensions.get(name)
+ if (ext) {
+ args[0].setDelegate(ext)
+ args[0].setResolveStrategy Closure.DELEGATE_ONLY
+ return args[0].call()
+ }
+ }
+
+ throw new MissingMethodException(name, delegate, args)
+ }
+
+ Map getProperties() {
+ Map properties =
+ [ name:name, description:description, version:version, vendor:vendor,
+ extensions:extensions.collectEntries { key, value -> [(key) : value.getProperties()] },
+ ]
+
+ addSupportedApplicationsToMap( properties )
+
+ return properties;
+ }
+
+ /**
+ * This handles specifying supported applications -- as we now have more than just WebCTRL
+ * add-ons. This was introduced in a WebCTRL 6.5 patch but needs to be backward compatible.
+ * To handle this, if you don't specify anything in supportedApplications, this will generate
+ * the same info.xml as it always did (which means WebCTRL will assume that webserver is the
+ * only supported application). If however, you specify anything in supportedApplications,
+ * then you must specify ALL applications you support.
+ */
+ private void addSupportedApplicationsToMap( Map properties )
+ {
+ if( supportedApplications.isEmpty() )
+ return;
+
+ supportedApplications.each {
+ properties.put( it, 'supported' )
+ }
+ }
+
+ private Map getPropMap() {
+ def map = [:]
+ if (name)
+ map['name'] = name
+ if (description)
+ map['description'] = description
+ if (version)
+ map['version'] = version
+ if (vendor)
+ map['vendor'] = vendor
+ return map
+ }
+
+ void addXml(root) {
+ propMap.each { key, value ->
+ root."$key" { root.mkp.yield(value) }
+ }
+ extensions.values().each { it.addXml(root) }
+
+ addSupportedApplicationsXml( root )
+ }
+
+ //see addSupportedApplicationsToMap. Same concept.
+ def addSupportedApplicationsXml( root )
+ {
+ if( supportedApplications.isEmpty() )
+ return;
+
+ root.supports {
+ def supportedElement = delegate;
+ supportedApplications.each { app ->
+ supportedElement.application( "${app}" )
+ }
+ }
+ }
}
+// support for providers in info.xml
+class ProviderExtension {
+ Map providers = [:]
+
+ String propNameToXmlElementName(String propName)
+ {
+ // this converts a camelCase string to dash-separated all lower case string (i.e. camel-case)
+ propName.replaceAll('[A-Z]', { '-'+it.toLowerCase() }) + '-provider'
+ }
+
+ def propertyMissing(name, value) { providers.put(name, value) }
+ Map getProperties() { return providers; }
+ void addXml(root) {
+ providers.each { String key, value ->
+ root."${propNameToXmlElementName(key)}" { root.mkp.yield(value) }
+ }
+ }
+}
+
+// support for license requirements in info.xml (only available if signing is requested)
+class LicenseRequirementsExtension {
+ Map entries = [:]
+
+ def methodMissing(String name, args) { entries.put(name, args) }
+ Map getProperties() { [entries: entries] }
+ void addXml(root) {
+ root.'license-requirements' {
+ def xmlNode = delegate
+ entries.each { name, args ->
+ xmlNode.invokeMethod(name, args)
+ }
+ }
+ }
+}