Skip to content

McAvity/gradle-aem-plugin

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cognifide logo

Gradle Status Apache License, Version 2.0, January 2004 Travis Build

Gradle AEM Plugin

Gradle AEM Plugin Logo

Description

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 :)


Example Project Build


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).

Table of contents

Installation

  • Most effective way to experience Gradle AEM Plugin is to use Quickstart located in:
  • 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.

Configuration

Plugin setup

Released versions of plugin are available on Bintray, so that this repository need to be included in buildscript section.

Minimal:

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
	
}

Additional

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
    }
}

Base plugin tasks

Task aemSync

Check out then clean JCR content.

Task aemCheckout

Check out JCR content from running AEM author instance to local content path.

Task aemClean

Clean checked out JCR content.

Task aemVlt

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.

Task aemRcp

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.

Task aemDebug

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
    }
    // ...
  }
}

Package plugin tasks

Task aemCompose

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.

Task aemDeploy

Upload & install CRX package into AEM instance(s). Primary, recommended form of deployment. Optimized version of aemUpload aemInstall.

Task aemUpload

Upload composed CRX package into AEM instance(s).

Task aemDelete

Delete uploaded CRX package from AEM instance(s).

Task aemInstall

Install uploaded CRX package on AEM instance(s).

Task aemUninstall

Uninstall uploaded CRX package on AEM instance(s).

Task aemPurge

Fail-safe combination of aemUninstall and aemDelete.

Task aemActivate

Replicate installed CRX package to other AEM instance(s).

Instance plugin tasks

Task aemSetup

Perform initial setup of local AEM instance(s). Automated version of aemCreate aemUp aemSatisfy aemDeploy.

Setup task

Task aemCreate

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

Task aemDestroy

Destroy local AEM instance(s).

Task aemUp

Turn on local AEM instance(s).

Task aemDown

Turn off local AEM instance(s).

Task aemReload

Reload OSGi Framework (Apache Felix) on local and remote AEM instance(s).

Task aemSatisfy

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 property aem.http.ignoreSSL to false.
  • 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 property aem.sftp.hostChecking to true.
  • 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 group tools, 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]

Task aemAwait

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.

Task aemCollect

Composes ZIP package from all CRX packages being satisfied and built. Available methods:

Screenshot below presents generated ZIP package which is a result of running gradlew :aemCollect for multi-module project.

Collect task - ZIP Overview

Expandable properties

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 format yDDmmssSSS).
  • 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 method includeContent of aemCompose 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 in aem.vlt.command property. Following properties are being used internally also by aemCheckout.
    • instances - map of defined instances with names as keys.

How to's

Set AEM configuration properly for all / concrete project(s)

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.

Work with local and/or remote AEM instances

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 detailed aemUpload, aemInstall and optionally aemActivate,
  • 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 from com.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.

Understand why there are one or two plugins to be applied in build script

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.

Work effectively on start and daily basis

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.

Deploy CRX package(s) only to filtered group of instances:

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

Deploy CRX package(s) only to instances specified explicitly

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

Deploy only filtered dependent CRX package(s)

Filters with wildcards, comma delimited.

gradlew aemSatisfy -Paem.satisfy.group=hotfix-*,groovy-console

Customize local AEM instances configuration

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.

Check out and clean JCR content using filter at custom path

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

Check out and clean JCR content using filter roots specified explicitly

gradlew :content:aemSync -Paem.checkout.filterRoots=[/etc/tags/example,/content/dam/example]

Assemble all-in-one CRX package(s)

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.

Include additional OSGi bundle into CRX package

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.

Embed JAR file into built OSGi bundle

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.

Exclude packages being incidentally imported by OSGi bundle

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
    } 
}

Skip installed package resolution by download name.

gradlew aemInstall -Paem.package.skipDownloadName=false

Only matters when Vault properties file is customized then that property could be used to eliminate conflicts.

Known issues

No OSGi services / components are registered

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.

Caching task aemCompose

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 tasks parallelism

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.

Files from SSH for aemCreate and aemSatisfy

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).

License

Gradle AEM Plugin is licensed under the Apache License, Version 2.0 (the "License")

About

Build rapidly AEM applications using Gradle Build System

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Kotlin 97.1%
  • Shell 2.1%
  • Other 0.8%