import javax.swing.* import java.awt.Dimension defaultTasks 'showGui' apply plugin: 'war' apply plugin: 'groovy' apply plugin: 'idea' apply plugin: 'eclipse' repositories { mavenCentral() // since JQuery is frequently used by add-ons, include their repo here as a convenience mavenRepo (name: "JQuery", url: "http://code.jquery.com") { pattern = "[module]-[revision](.[classifier]).[ext]" } mavenRepo (name: "JQuery UI", url: "http://code.jquery.com/ui") { pattern = "[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. } project.convention.plugins.addoninfo = new AddOnInfo() project.convention.plugins.addoninfo.addExtension('providers', new ProviderExtension()) final addonInfoFile = new File(buildDir, 'tmp/war/info.xml') task generateAddOnInfo { 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() } } war { dependsOn generateAddOnInfo inputs.files addonInfoFile, 'LICENSE.txt' extension = 'addon' // lower-case'ify the war name to simplify usage in a web browser (which is case-sensitive) doFirst { archiveName = war.baseName.toLowerCase() + '.' + extension } fileMode = 0644 into('webapp') // put most stuff into the 'webapp' subdir // move the stuff that the War task puts into 'WEB-INF' to 'webapp/WEB-INF' copyAction.rootSpec.includeEmptyDirs = false copyAction.rootSpec.eachFile { it.mode = 0644 if (it.path.startsWith('WEB-INF')) it.path = 'webapp/'+it.path } // include addon-info.xml in binary copyAction.rootSpec.into('') { from(addonInfoFile) } // include license in binary from('LICENSE.txt') { into 'WEB-INF' } // 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(); } // 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. 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.set("getServerDir", {getServerDir()}); // Helper methods to get the directory in which to deploy webapps (add-ons). File getDeployLoc() { new File(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 deploy.destDir } // task that builds and deploys the .war to the webserver task deploy(description: 'Deploys the war to the webserver', type: Sync, dependsOn:[sourceSets.main.runtimeClasspath, generateAddOnInfo]) { ext.destDir = getDeployLoc() doFirst { println "Deploying $war.baseName to ${ext.destDir.canonicalPath}" } with war into { ext.destDir } fileMode = 0644 } // have war tell you where the archive was put! war.doLast { println "War created as ${war.archivePath}" } // if needed, specify a special repo for the keystore if (hasProperty('addon_sign_repo')) { repositories { mavenRepo(name:'keystore_repo', url:addon_sign_repo) } } // 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 war { 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 } } else { def keyDep = dependencies.create(addon_sign_keystore_gav) def keystore = configurations.detachedConfiguration(keyDep).resolve().iterator().next() ant.signjar(jar: war.archivePath, alias:addon_sign_alias, storepass:addon_sign_password, keystore:keystore.path, keypass:addon_sign_keypass) } } // 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 } def projRef = project rootProject.idea { 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() 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 webNode = mgrNode?.artifact.find { it.'@name' == 'Add-On Web exploded' } if (webNode) mgrNode.remove(webNode) mgrNode.append(new XmlParser().parseText(""" ${pathFactory.relativePath('PROJECT_DIR', new File(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.relativePath(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.relativePath(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(''' ''')) } } } buildscript { repositories { mavenRepo url: 'http://download.java.net/maven/2/' } dependencies { classpath group: 'com.jidesoft', name: 'jide-oss', version: '2.9.0' } } // Support for info.xml class AddOnInfo { def String name def String description def String version def String vendor Map extensions = [:] 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() { [ name:name, description:description, version:version, vendor:vendor, extensions:extensions.collectEntries { key, value -> [(key) : value.getProperties()] } ] } 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) } } } // 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 { key, value -> root."${propNameToXmlElementName(key)}" { root.mkp.yield(value) } } } } // support for license requirements in info.xml (only available if signing is requested class LicenseRequirementsExtension { List features = [] Closure markup = {} Map getProperties() { return [features:features]; } void addXml(root) { root.'license-requirements' { features.each { str -> feature { mkp.yield(str) } } markup.delegate = root markup(); } } }