Currently there is no popular way to build applications for AEM using Gradle build system. This project contains brand new Gradle plugin to assemble CRX package and deploy it on instance(s).
Incremental build which takes seconds, not minutes. Developer who does not loose focus between build time gaps. Extend freely your build system directly in project.
AEM developer - it's time to meet Gradle! You liked or used plugin? Don't forget to star this project on GitHub :)
Features:
- Fully automated, tied to project, local AEM instance(s) setup allowing to start development within few minutes.
- Composing CRX package from multiple JCR content roots, bundles.
- Automated all-in-one CRX packages generation (assemblies).
- Easy multi-deployment with instance groups.
- Automated dependent CRX packages and OSGi bundles installation from local and remote sources (SMB, SSH, HTTP(s)).
- Smart Vault files generation (combining defaults with overiddables).
- Embedded Vault tool for checking out and cleaning JCR content from running AEM instance.
- OSGi Manifest customization by embedded BND plugin.
- OSGi Declarative Services annotations support (instead of SCR, see docs).
- Installation
- Configuration
- How to's
- Set AEM configuration properly for all / concrete project(s)
- Work with local and/or remote AEM instances
- Understand why there are one or two plugins to be applied in build script
- Work effectively on start and daily basis
- Deploy CRX package(s) only to filtered group of instances:
- Deploy CRX package(s) only to instances specified explicitly
- Deploy only filtered dependent CRX package(s)
- Customize local AEM instances configuration
- Check out and clean JCR content using filter at custom path
- Check out and clean JCR content using filter roots specified explicitly
- Assemble all-in-one CRX package(s)
- Include additional OSGi bundle into CRX package
- Embed JAR file into built OSGi bundle
- Exclude packages being incidentally imported by OSGi bundle
- Skip installed package resolution by download name.
- Known issues
- License
- Most effective way to experience Gradle AEM Plugin is to use Quickstart located in:
- AEM Single-Project Example - recommended for application development,
- AEM Multi-Project Example - recommended for project development,
- The only needed software to start using plugin is to have installed on machine Java 8.
- As a build command, it is recommended to use Gradle Wrapper (
gradlew
) instead of locally installed Gradle (gradle
) to easily have same version of build tool installed on all environments. Only at first build time, wrapper will be automatically downloaded and installed, then reused.
Released versions of plugin are available on Bintray, so that this repository need to be included in buildscript section.
Configuration below assumes building and deploying on AEM instance(s) via command: gradlew aemDeploy
.
File settings.gradle:
pluginManagement {
repositories {
jcenter()
maven { url "http://dl.bintray.com/cognifide/maven-public" }
}
resolutionStrategy {
eachPlugin {
if (requested.id.namespace == 'com.cognifide.aem') {
useModule('com.cognifide.gradle:aem-plugin:4.0.5')
}
}
}
}
File build.gradle:
plugins {
id 'com.cognifide.aem.bundle' // or 'package' for JCR content only
}
AEM configuration section contains all default values for demonstrative purpose.
More detailed and always up-to-date information about all configuration options is available here.
Configuration below assumes building and deploying on AEM instance(s) via command: gradlew
(default tasks will be used).
plugins {
id 'com.cognifide.aem.bundle'
id 'com.cognifide.aem.instance'
id 'org.jetbrains.kotlin.jvm' // or any other like 'java' to compile OSGi bundle
}
defaultTasks = [':aemSatisfy', ':aemDeploy', ':aemAwait']
aem {
config {
environment = "local" // -Paem.env or environment variable: AEM_ENV
instanceName = "${environment}-*"
instanceAuthorName = "${environment}-author"
instanceConnectionTimeout = 5000
instanceConnectionUntrustedSsl = true
contentPath = project.file("src/main/content")
if (project == project.rootProject) {
bundlePath = "/apps/${project.name}/install"
} else {
bundlePath = "/apps/${project.rootProject.name}/${project.name}/install"
}
bundlePackage = ""
bundlePackageOptions = "-split-package:=merge-first"
bundleManifestAttributes = true
bundleBndPath = "${project.file('bnd.bnd')}"
if (projectUniqueName) {
packageName = project.name
} else {
packageName = "${projectNamePrefix}-${project.name}"
}
packageLocalPath = ""
packageRemotePath = ""
packageFilesExcluded = [
"**/.gradle",
"**/.git",
"**/.git/**",
"**/.gitattributes",
"**/.gitignore",
"**/.gitmodules",
"**/.vlt",
"**/node_modules/**",
"jcr_root/.vlt-sync-config.properties"
]
packageFilesExpanded = [
"**/META-INF/vault/*.xml"
]
packageFileProperties = []
packageBuildDate = Date()
packageAcHandling = "merge_preserve"
packageSnapshots = []
vaultCopyMissingFiles = true
vaultFilesPath = project.rootProject.file("src/main/resources/META-INF/vault")
vaultLineSeparator = "LF"
deployDistributed = false
uploadForce = true
uploadRetryTimes = 6
uploadRetryDelay = 30000
installRecursive = true
installRetryTimes = 3
installRetryDelay = 30000
createPath = "${System.getProperty("user.home")}/.aem/${project.rootProject.name}"
createFilesPath = project.rootProject.file("src/main/resources/local-instance")
createFilesExpanded = [
"**/*.properties",
"**/*.sh",
"**/*.bat",
"**/*.xml",
"**/start",
"**/stop"
]
upInitializer = { handle -> }
awaitStableDelay = 3000
awaitStableInterval = 1000
awaitStableTimes = 300
awaitStableAssurances = 3
awaitStableState = { it.checkBundleState(500) }
awaitStableCheck = { it.checkBundleStable(500) }
awaitHealthCheck = { it.checkComponentState(["com.day.crx.packaging.*", "org.apache.sling.installer.*"], 10000) }
awaitHealthRetryTimes = 3L
awaitHealthRetryDelay = 30000
awaitFast = false
awaitFastDelay = 1000
awaitResume = false
reloadDelay = 10000
satisfyRefreshing = false
satisfyBundlePath = "/apps/gradle-aem-plugin/satisfy/install"
satisfyBundleProperties = { bundle -> [:] }
satisfyGroupName = "*"
checkoutFilterPath = ""
cleanFilesDeleted = [
"**/.vlt",
"**/.vlt*.tmp",
"**/jcr_root/.content.xml",
"**/jcr_root/apps/.content.xml",
"**/jcr_root/conf/.content.xml",
"**/jcr_root/content/.content.xml",
"**/jcr_root/content/dam/.content.xml",
"**/jcr_root/etc/.content.xml",
"**/jcr_root/etc/designs/.content.xml",
"**/jcr_root/home/.content.xml",
"**/jcr_root/home/groups/.content.xml",
"**/jcr_root/home/users/.content.xml",
"**/jcr_root/libs/.content.xml",
"**/jcr_root/system/.content.xml",
"**/jcr_root/tmp/.content.xml",
"**/jcr_root/var/.content.xml"
]
cleanSkipProperties = [
"jcr:uuid!**/home/users/*,**/home/groups/*",
"jcr:lastModified",
"jcr:created",
"cq:lastModified*",
"cq:lastReplicat*",
"*_x0040_Delete",
"*_x0040_TypeHint"
]
notificationEnabled = false
}
}
Check out then clean JCR content.
Check out JCR content from running AEM author instance to local content path.
Clean checked out JCR content.
Execute any JCR File Vault command.
For instance, to copy nodes from one remote AEM instance to another, there might be used command below:
gradlew :content:aemVlt -Paem.vlt.command='rcp -b 100 -r -u -n http://admin:admin@localhost:4502/crx/-/jcr:root/content/dam/example http://admin:admin@localhost:4503/crx/-/jcr:root/content/dam/example'
For more details about available parameters, please visit VLT Tool documentation.
While using task aemVlt
be aware that Gradle requires to have working directory with file build.gradle in it, but Vault tool can work at any directory under jcr_root. To change working directory for Vault, use property aem.vlt.path
which is relative path to be appended to jcr_root for project task being currently executed.
Copy JCR content from one instance to another. Sample usages below.
Using predefined instances with multiple different source and target nodes:
gradlew :aemRcp -Paem.rcp.source.instance=int-author -Paem.rcp.target.instance=local-author -Paem.rcp.paths=[/content/example-demo=/content/example,/content/dam/example-demo=/content/dam/example]
Using predefined instances with multiple same source and target nodes:
gradlew :aemRcp -Paem.rcp.source.instance=stg-author -Paem.rcp.target.instance=int-author -Paem.rcp.paths=[/content/example,/content/example2]
Using dynamically defined instances:
gradlew :aemRcp -Paem.rcp.source.instance=http://user:[email protected]:4502 -Paem.rcp.target.instance=http://user:[email protected]:4502 -Paem.rcp.paths=[/content/example-demo=/content/example]
Keep in mind, that copying JCR content between instances, could be a trigger for running AEM workflows like DAM Update Asset which could cause heavy load on instance. Consider disabling AEM workflow launchers before running this task and re-enabling after.
RCP task is internally using Vault Remote Copy which requires to having bundle Apache Sling Simple WebDAV Access to repositories (org.apache.sling.jcr.webdav) " in active state on instance.
Dumps effective AEM build configuration of concrete project to JSON file.
When command below is being run (for root project :
):
gradlew :aemDebug
Then file at path build/aem/aemDebug/debug.json with content below is being generated:
{
"buildInfo" : {
"plugin" : {
"pluginVersion" : "x.y.z",
"gradleVersion" : "x.y.z"
}
},
"projectInfo" : {
"displayName" : "root project 'example'",
"path" : ":",
"name" : "example",
"dir" : "C:\\Users\\krystian.panek\\Projects\\gradle-aem-multi"
},
"packageProperties" : {
"name" : "example",
"config" : {
"instances" : {
"local-author" : {
"httpUrl" : "http://localhost:4502",
"user" : "admin",
"password" : "admin",
"typeName" : "author",
"debugPort" : 14502,
"name" : "local-author",
"type" : "AUTHOR",
"httpPort" : 4502,
"environment" : "local"
}
// ...
},
"uploadForce" : true,
"installRecursive" : true
// ...
},
"requiresRoot" : "false",
"buildCount" : "20173491654283",
"created" : "2017-12-15T07:16:54Z"
},
"packageDeployed" : {
"local-author" : {
"group" : "com.company.aem",
"name" : "example",
"version" : "1.0.0-SNAPSHOT",
"path" : "/etc/packages/com.company.aem/example-1.0.0-SNAPSHOT.zip",
"downloadName" : "example-1.0.0-SNAPSHOT.zip",
"lastUnpacked" : 1513321701062,
"installed" : true
}
// ...
}
}
Compose CRX package from JCR content and bundles. Available methods:
includeProject(projectPath: String)
, includes both bundles and JCR content from another project, example:includeProject ':core'
.includeContent(projectPath: String)
, includes only JCR content, example:includeContent ':design'
.includeBundles(projectPath: String)
, includes only bundles, example:includeBundles ':common'
.includeBundlesAtPath(projectPath: String, installPath: String)
, includes only bundles at custom install path, example:includeBundles(':common', '/apps/my-app/install')
.includeBundles(projectPath: String, runMode: String)
, as above, useful when bundles need to be installed only on specific type of instance.mergeBundles(projectPath: String)
, includes only bundles at same install path.mergeBundles(projectPath: String, runMode: String)
, as above, useful when bundles need to be installed only on specific type of instance.includeProjects(pathPrefix: String)
, includes both bundles and JCR content from all AEM projects (excluding itself) in which project path is matching specified filter. Vault filter roots will be automatically merged and available in property${filterRoots}
in filter.xml file. Useful for building assemblies (all-in-one packages).includeSubprojects()
, alias for method above:includeProjects("${project.path}:*")
.- all inherited from ZIP task.
Upload & install CRX package into AEM instance(s). Primary, recommended form of deployment. Optimized version of aemUpload aemInstall
.
Upload composed CRX package into AEM instance(s).
Delete uploaded CRX package from AEM instance(s).
Install uploaded CRX package on AEM instance(s).
Uninstall uploaded CRX package on AEM instance(s).
Fail-safe combination of aemUninstall
and aemDelete
.
Replicate installed CRX package to other AEM instance(s).
Perform initial setup of local AEM instance(s). Automated version of aemCreate aemUp aemSatisfy aemDeploy
.
Create local AEM instance(s). To use it specify required properties in ignored file gradle.properties at project root (protocols supported: SMB, SSH, HTTP(s) or local path, SMB as example):
aem.instance.local.jarUrl=smb://[host]/[path]/cq-quickstart.jar
aem.instance.local.licenseUrl=smb://[host]/[path]/license.properties
aem.smb.domain=MYDOMAIN
aem.smb.username=MYUSER
aem.smb.password=MYPASSWORD
Destroy local AEM instance(s).
Turn on local AEM instance(s).
Turn off local AEM instance(s).
Reload OSGi Framework (Apache Felix) on local and remote AEM instance(s).
Upload & install dependent CRX package(s) before deployment. Available methods:
local(path: String)
, use CRX package from local file system.local(file: File)
, same as above, but file can be even located outside the project.url(url: String)
, use CRX package that will be downloaded from specified URL to local temporary directory.downloadHttp(url: String)
, download package using HTTP with no auth.downloadHttpAuth(url: String, username: String, password: String)
, download package using HTTP with Basic Auth support.downloadHttpAuth(url: String)
, as above, but credentials must be specified in variables:aem.http.username
,aem.http.password
. Optionally enable SSL errors checking by setting propertyaem.http.ignoreSSL
tofalse
.downloadSmbAuth(url: String, domain: String, username: String, password: String)
, download package using SMB protocol.downloadSmbAuth(url: String)
, as above, but credentials must be specified in variables:aem.smb.domain
,aem.smb.username
,aem.smb.password
.downloadSftpAuth(url: String, username: String, password: String)
, download package using SFTP protocol.downloadSftpAuth(url: String)
, as above, but credentials must be specified in variables:aem.sftp.username
,aem.sftp.password
. Optionally enable strict host checking by setting propertyaem.sftp.hostChecking
totrue
.dependency(notation: String)
, use OSGi bundle that will be resolved from defined repositories (for instance from Maven) then wrapped to CRX package:dependency('com.neva.felix:search-webconsole-plugin:1.2.0')
.group(name: String, configurer: Closure)
, useful for declaring group of packages (or just optionally naming single package) to be installed only on demand. For instance:group 'tools', { url('http://example.com/package.zip'); url('smb://internal-nt/package2.zip') }
. Then to install only packages in grouptools
, use command:gradlew aemSatisfy -Paem.satisfy.group=tools
.
Example configuration:
aemSatisfy {
local "pkg/vanityurls-components-1.0.2.zip"
url "https://github.com/Cognifide/APM/releases/download/cqsm-3.0.0/apm-3.0.0.zip"
url "smb://company-share/aem/packages/my-lib.zip"
url "sftp://company-share/aem/packages/other-lib.zip"
url "file:///C:/Libraries/aem/package/extra-lib.zip"
dependency 'com.neva.felix:search-webconsole-plugin:1.2.0'
}
It is also possible to specify packages to be deployed only once via command line parameter. Also for local files at any file system paths.
gradlew aemSatisfy -Paem.satisfy.urls=[url1,url2]
For instance:
gradlew aemSatisfy -Paem.satisfy.urls=[https://github.com/OlsonDigital/aem-groovy-console/releases/download/11.0.0/aem-groovy-console-11.0.0.zip,https://github.com/neva-dev/felix-search-webconsole-plugin/releases/download/search-webconsole-plugin-1.2.0/search-webconsole-plugin-1.2.0.jar]
Wait until all local or remote AEM instance(s) be stable.
AEM Config Param | CMD Property | Default Value | Purpose |
---|---|---|---|
awaitStableInterval |
aem.await.stable.interval | 1000 |
Time in milliseconds used as interval between next instance stability checks being performed. Optimization could be necessary only when instance is heavily loaded. |
awaitStableTimes |
aem.await.stable.times | 300 |
Maximum intervals after which instance stability checks will be skipped if there is still some unstable instance left. |
awaitStableAssurances |
aem.await.stable.assurances | 3 |
Number of intervals / additional instance stability checks to assure all stable instances. |
awaitStableCheck |
n/a | { it.checkBundleStable(500) } |
Hook for customizing instance stability check. Check will be repeated if assurance is configured. |
awaitHealthCheck |
n/a | { it.checkComponentState(10000) } |
Hook for customizing instance health check. |
awaitHealthRetryTimes |
aem.await.health.retry.times | 3 |
Repeat health check when failed (brute-forcing). |
awaitHealthRetryDelay |
aem.await.health.retry.delay | 30000 |
Time to wait after repeating failed health check. |
awaitFast |
aem.await.fast | false |
Skip stable check assurances and health checking. Alternative, quicker type of awaiting stable instances. |
awaitFastDelay |
aem.await.fast.delay | 1000 |
Time in milliseconds to postpone instance stability checks to avoid race condition related with actual operation being performed on AEM like starting JCR package installation or even creating launchpad. |
awaitResume |
aem.await.resume | false |
Do not fail build but log warning when there is still some unstable or unhealthy instance. |
Instance state, stable check, health check lambdas are using: InstanceState. Use its methods to achieve expected customized behavior.
Composes ZIP package from all CRX packages being satisfied and built. Available methods:
- all inherited from ZIP task.
Screenshot below presents generated ZIP package which is a result of running gradlew :aemCollect
for multi-module project.
By default, plugin is configured that in all XML files located under path META-INF/vault properties can be injected using syntax: {{property}}
.
The properties syntax comes from Pebble Template Engine which means that all its features (if statements, for loops, filters etc) can be used inside files being expanded.
Related configuration:
aem {
config {
fileProperties = [
"organization": "My Company"
]
filesExpanded = [
"**/META-INF/vault/*.xml"
]
}
}
This feature is specially useful to generate valid META-INF/properties.xml file, below is used by plugin by default:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>{{project.description}}</comment>
<entry key="group">{{project.group}}</entry>
<entry key="name">{{config.packageName}}</entry>
<entry key="version">{{project.version}}</entry>
<entry key="groupId">{{project.group}}</entry>
<entry key="artifactId">{{project.name}}</entry>
<entry key="description">{{project.description}}</entry>
<entry key="createdBy">{{user.name}}</entry>
<entry key="acHandling">{{config.packageAcHandling}}</entry>
<entry key="requiresRoot">{{requiresRoot}}</entry>
</properties>
Predefined properties:
config
- AEM configuration.rootProject
- project with directory in which settings.gradle is located.project
- current project.buildCount
- number to be used as CRX package build count (buildDate
in formatyDDmmssSSS
).created
- current date in ISO8601 format.
Task specific:
aemCompose
- properties which are being dynamically calculated basing on content actually included into package.filterRoots
- after using methodincludeContent
ofaemCompose
task, all Vault filter roots are being gathered. This property contains all these XML tags concatenated especially useful for building assemblies. If no projects will be included, then this variable will contain a single filter root with bundle path to be able to deploy auto-generated package with JAR file only.filters
- same as above, but instead it is a collection of XML elements that could be traversed and rendered in a customized way.
aemVlt
- properties are being injected to command specified inaem.vlt.command
property. Following properties are being used internally also byaemCheckout
.instances
- map of defined instances with names as keys.
Global configuration like AEM instances should be defined in root build.gradle file:
allprojects { subproject ->
plugins.withId 'com.cognifide.aem.base', {
aem {
config {
contentPath = subproject.file("src/main/aem") // overrides default dir named 'content'
}
}
}
}
For instance, project :app:core
specific configuration like OSGi bundle or CRX package options should be defined in app/core/build.gradle
:
aem {
config {
bundlePackage = 'com.company.aem.example.core'
}
}
aemCompose {
includeProjects ':content:*'
baseName = 'example-core'
duplicatesStrategy = "EXCLUDE"
}
Warning! Very often plugin users mistake is to configure aemSatisfy
task in allprojects
closure.
As an effect there will be same dependent CRX package defined multiple times.
In AEM configuration section, there is possibility to use localInstance
or remoteInstance
methods to define AEM instances to be used to:
- install CRX packages being built via command
aemDeploy
or combination of more detailedaemUpload
,aemInstall
and optionallyaemActivate
, - communicate with while using Vault tool in commands
aemSync
,aemCheckout
,aemVlt
, - install dependent packages while using
aemSatisfy
command.
aem {
config {
localInstance "http://localhost:4502" // local-author
localInstance "http://localhost:4502", { // local-author
user = "admin"
password = "admin"
typeName = "author"
debugPort = 14502
}
localInstance "http://localhost:4503" // local-publish
localInstance "http://localhost:4503", { // local-publish
user = "admin"
password = "admin"
typeName = "publish"
debugPort = 14503
}
remoteInstance "http://192.168.10.1:4502", { // integration-author1
user = "user1"
password = "password2"
environment = "integration"
typeName = "author1"
}
remoteInstance "http://192.168.10.1:8080", { // integration-author2
user = "user1"
password = "password2"
environment = "integration"
typeName = "author2
}
remoteInstance "http://192.168.10.2:4503", { // integration-publish1
user = "user2"
password = "password2"
environment = "integration"
typeName = "publish1"
}
remoteInstance "http://192.168.10.2:8080", { // integration-publish2
user = "user2"
password = "password2"
environment = "integration"
typeName = "publish2"
}
}
}
The above configuration can be also specified through gradle.properties file using dedicated syntax (recommended approach).
aem.instance.$TYPE.$ENVIRONMENT-$TYPE_NAME.$PROP_NAME=$PROP_VALUE
Part | Possible values | Description |
---|---|---|
$TYPE |
local or remote (only) |
Type of instance. Local means that for each one there will be set up AEM Quickstart at local file system. |
$ENVIRONMENT |
local , int , stg etc |
Environment name. |
$TYPE_NAME |
author , publish , publish2 , etc |
Combination of AEM instance type and semantic suffix useful when more than one of instance of same type is being configured. |
$PROP_NAME=$PROP_VALUE |
Local instances: httpUrl=http://admin:admin@localhost:4502 , password=foo , runModes=nosamplecontent , jvmOpts=-server -Xmx2048m -XX:MaxPermSize=512M -Djava.awt.headless=true , startOpts=... , debugPort=24502 . Remote instances: httpUrl , user , password . |
Run modes, JVM opts and start opts should be comma delimited. |
Rules:
- Instance name is a combination of ${environment}-${typeName} e.g local-author, integration-publish etc.
- Instance type name must start with prefix author or publish. Sample valid names: author, author1, author2, author-master and publish, publish1 publish2 etc.
- Only instances being defined as local are being considered in command
aemSetup
,aemCreate
,aemUp
etc (that comes fromcom.cognifide.aem.instance
plugin). - All instances being defined as local or remote are being considered in commands CRX package deployment related like
aemSatisfy
,aemDeploy
,aemUpload
,aemInstall
etc.
Gradle AEM plugin architecture is splitted into 4 plugins to properly fit into Gradle tasks structure correctly.
- base (
com.cognifide.aem.base
), applied transparently by other plugins, provides AEM config section to build script and general tasks:aemDebug
,aemVlt
etc. - instance (
com.cognifide.aem.instance
), should be applied only at root project (once), provides instance related tasks:aemAwait
,aemSetup
,aemCreate
etc, - package (
com.cognifide.aem.package
), should be applied to all projects that are composing CRX packages from JCR content only, provides CRX package related tasks:aemCompose
,aemDeploy
etc. - bundle (
com.cognifide.aem.bundle
), should be applied to all projects that are composing CRX packages from both OSGi bundle being built and optionally JCR content, extends package plugin.
Most often, Gradle commands are being launched from project root and tasks are being run by their name e.g aemSatisfy
(which is not fully qualified, better if it will be :aemSatisfy
of root project).
Let's imagine if task aemSatisfy
will come from package plugin, then Gradle will execute more than one aemSatisfy
(for all projects that have plugin applied), so that this is unintended behavior.
Currently used plugin architecture solves that problem.
Initially, to create fully configured local AEM instances simply run command gradlew aemSetup
.
Later during development process, building and deploying to AEM should be done using most simple command: gradlew
.
Above configuration uses default tasks, so that alternatively it is possible to build same using explicitly specified command gradlew aemSatisfy aemDeploy aemAwait
.
- Firstly dependent packages (like AEM hotfixes, Vanity URL Components etc) will be installed lazily (only when they are not installed yet).
- In next step application is being built and deployed to all configured AEM instances.
- Finally build awaits till all AEM instances be stable.
When there are defined named AEM instances: local-author
, local-publish
, integration-author
and integration-publish
,
then it is available to deploy packages with taking into account:
- type of environment (local, integration, staging, etc)
- type of AEM instance (author / publish)
gradlew aemDeploy -Paem.instance.name=integration-*
gradlew aemDeploy -Paem.instance.name=*-author
Default value of that instance name filter is local-*
.
To deploy only to author instance(s) or publish instance(s):
gradlew aemDeploy -Paem.instance.authors
gradlew aemDeploy -Paem.instance.publishers
Instance urls delimited by semicolon:
gradlew aemDeploy -Paem.instance.list=http://admin:admin@localhost:4502;http://admin:admin@localhost:4503
Alternative syntax - list delimited: instances by semicolon, instance properties by comma.
gradlew aemDeploy -Paem.instance.list=http://localhost:4502,admin,admin;http://localhost:4503,admin,admin
Filters with wildcards, comma delimited.
gradlew aemSatisfy -Paem.satisfy.group=hotfix-*,groovy-console
Plugin allows to override or provide extra files to local AEM instance installations. This behavior is controlled by:
aem {
config {
createPath = "${System.getProperty("user.home")}/.aem/${project.rootProject.name}"
createFilesPath = project.rootProject.file("src/main/resources/local-instance")
createFilesExpanded = [
"**/*.properties",
"**/*.sh",
"**/*.bat",
"**/*.xml",
"**/start",
"**/stop"
]
}
}
- Property createPath determines where AEM instance files will be extracted on local file system.
- Property createFilesPath determines project location that holds extra instance files that will override plugin defaults (start / stop scripts) and / or extracted AEM files.
- Property createFilesExpandable specifies which AEM instance files have an ability to use expandable properties inside.
In other words, to customize instance files just:
- Copy default start / stop scripts to project path controlled by createFilesPath
- Customize copied scripts.
- Provide other AEM files that need to be added or overridden.
E.g for subproject :content
:
gradlew :content:aemSync -Paem.checkout.filterPath=custom-filter.xml
gradlew :content:aemSync -Paem.checkout.filterPath=src/main/content/META-INF/vault/custom-filter.xml
gradlew :content:aemSync -Paem.checkout.filterPath=C:/aem/custom-filter.xml
gradlew :content:aemSync -Paem.checkout.filterRoots=[/etc/tags/example,/content/dam/example]
Let's assume following project structure (with build file contents):
- build.gradle (project
:
)
apply plugin: 'com.cognifide.aem.package'
aemCompose {
includeProjects(':app:*')
includeProjects(':content:*')
includeProject(':migration')
}
When building via command gradlew :build
, then the effect will be a CRX package with assembled JCR content and OSGi bundles from projects: :app:core
, :app:common
, :content:init
, :content:demo
and :migration
.
- app/build.gradle (project
:app
)
apply plugin: 'com.cognifide.aem.package'
aemCompose {
includeSubprojects()
}
When building via command gradlew :app:build
, then the effect will be a CRX package with assembled JCR content and OSGi bundles from projects: :app:core
, :app:common
only.
- app/core/build.gradle (project
:app:core
, JCR content and OSGi bundle) - app/common/build.gradle (project
:app:common
, JCR content and OSGi bundle) - content/init/build.gradle (project
:content:init
, JCR content only) - content/demo/build.gradle (project
:content:demo
, JCR content only) - migration/build.gradle (project
:migration
, JCR content only) - test/integration/build.gradle (project
:test:integration
, any source code) - test/functional/build.gradle (project
:test:functional
, any source code)
Gradle AEM Plugin is configured in that way that project can have:
- JCR content
- source code to compile OSGi bundle
- both
By distinguishing includeProject
, includeBundle
or includeContent
there is ability to create any assembly CRX package with content of any type without restructuring the project.
Only one must have rule to be kept while developing a multi-module project is that all Vault filter roots of all projects must be exclusive.
In general, they are most often exclusive, to avoid strange JCR installer behaviors, but sometimes exceptional workspace filter rules are being applied like mode="merge"
etc.
Simply use custom configuration named aemInstall
in dependencies
section.
In build script, instead using compile 'group:name:version'
use aemInstall 'group:name:version'
. As a result, artifact will be copied into CRX package being composed at path determined by property config.bundlePath
.
For the reference, see usage in AEM Multi-Project Example.
Simply use custom configuration named aemEmbed
in dependencies
section.
In build script, instead using compile 'group:name:version'
use aemEmbed 'group:name:version'
. As a result, artifact classes will be copied and become a part of OSGi bundle being built.
To be able to use classes provided by that dependency, there is needed to specify its packages to be available at bundle class path by using bundle DSL:
aem {
bundle {
exportPackage 'com.group.name' // package located in jar
attribute 'Export-Package', 'com.group.name.*' // alternatively
// or even with better encapsulation
privatePackage 'com.group.name'
attribute 'Private-Package', 'com.group.name.*' // alternatively
}
}
For the reference, see usage in AEM Multi-Project Example.
Sometimes BND tool could generate Import-Package directive that will import too many OSGi classes to be available on bundle class path. Especially when we are migrating non-OSGi dependency to OSGi bundle (because of transitive class dependencies).
To prevent that we could generate own manifest entry that will prevent importing optional classes.
For instance:
aem {
bundle {
excludePackage 'org.junit', 'org.mockito'
attribute 'Import-Package', '!org.junit,!org.mockito,*' // alternatively
}
}
gradlew aemInstall -Paem.package.skipDownloadName=false
Only matters when Vault properties file is customized then that property could be used to eliminate conflicts.
Since AEM 6.2 it is recommended to use new OSGi service component annotations to register OSGi components instead SCR annotations (still supported, but not by Gradle AEM Plugin).
For the reference, please read post on official Adobe Blog.
Basically, Gradle AEM Plugin is designed to be used while implementing new projects on AEM in version greater than 6.2. Because, of that fact, there is no direct possibility to reuse code written for older AEM's which is using SCR annotations. However it is very easy to migrate these annotations to new ones and generally speaking it is not much expensive task to do.
import org.apache.felix.scr.annotations.Component;
->
import org.osgi.service.component.annotations.Component;
New API fully covers functionality of old one, so nothing to worry about while migrating.
Expandable properties with dynamically calculated value (unique per build) like created
and buildCount
are not used by default generated properties file intentionally,
because such usages will effectively forbid caching aemCompose
task and it will be never UP-TO-DATE
.
Vault tool current working directory cannot be easily configured, because of its API. AEM plugin is temporarily changing current working directory for Vault, then returning it back to original value. In case of that workaround, Vault tasks should not be run in parallel (by separated daemon processed / JVM synchronization bypassed), because of potential unpredictable behavior.
Local instance JAR file can be provided using SSH, but SSHJ client used in implementation has an integration issue related with JDK and Crypto Policy.
As a workaround, just run build without daemon (--no-daemon
).
Gradle AEM Plugin is licensed under the Apache License, Version 2.0 (the "License")