Skip to content

Commit

Permalink
Moved all the Liquibase API calls to the 'apply' phase of things to a…
Browse files Browse the repository at this point in the history
…void classpath problems caused by the different jar that gets used at execute time
  • Loading branch information
stevesaliman committed Sep 7, 2024
1 parent abf48ac commit f9cb150
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 131 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ gradle.taskGraph.whenReady { taskGraph ->
// disable those tasks.

// Comment this to publish to Gradle, Uncomment for Maven Central
// taskGraph.allTasks.findAll { it.name ==~ /.*MavenPublication.*/ }*.enabled = false
taskGraph.allTasks.findAll { it.name ==~ /.*MavenPublication.*/ }*.enabled = false

// Only *require* signing if we are uploading a release version. If we do need to sign, make
// sure we've got the properties we need to do the signing.
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# What version of the Liquibase Gradle Plugin are we building?
liquibaseGradlePluginVersion=3.0.0
liquibaseGradlePluginVersion=3.0.1-SNAPSHOT

# When we download a maven dependency, should we also get source files?
downloadSources=false
Expand Down
169 changes: 62 additions & 107 deletions src/main/groovy/org/liquibase/gradle/ArgumentBuilder.groovy
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.liquibase.gradle


import liquibase.command.CommandArgumentDefinition
import liquibase.command.CommandDefinition
import liquibase.configuration.ConfigurationDefinition
import liquibase.configuration.LiquibaseConfiguration
import liquibase.Scope
Expand All @@ -24,6 +22,13 @@ import liquibase.Scope
* the Gradle property representation of a Liquibase argument. For example, if Liquibase has an
* argument named "changelogFile", users can define "-PLiquibaseChangelogFile" to pass an argument
* at runtime.
* <p>
* This class has 2 main parts. The first part configures the argument builder when the plugin is
* applied, and the second uses that configuration at execution time to build the correct argument
* list. It is important do do all the configuration at apply time because the Liquibase classes
* used at execution time will come from a different jar, and Liquibase API calls will return the
* wrong things. This is because Gradle uses the buildscript classpath at apply time, and the
* liquibaseRuntime classpath at execution time. Oh, the fun we have with classpath issues. *
*
* @author Steven C. Saliman
*/
Expand All @@ -33,59 +38,80 @@ class ArgumentBuilder {
// All Known Liquibase global arguments, as they can be passed in as properties.
static Set<String> allGlobalProperties
// All known Liquibase command arguments.
static Set<String> allCommandArguments
static Set<String> allCommandArguments = new HashSet<>()
// All known Liquibase command arguments, as they can be passed in as properties.
static Set<String> allCommandProperties
// All known Liquibase commands.
static Set<String> allCommands = new HashSet<>()
static Set<String> allCommandProperties = new HashSet<>()

// The Gradle project, used for logging.
def project

/**
* Add a Liquibase command to our collection of known commands. The plugin adds them one at a
* time as it creates tasks. The Argument Builder will then use this list to figure out what
* command arguments are supported. We won't take the time to figure out supported arguments
* unless we're actually running a liquibase task.
* Initialize the global argument and global properties sets. This needs to be done at apply
* time so that we get arguments from the same classpath that was used to create the tasks.
* <p>
* This method asks Liquibase for all the supported global arguments. Each one is added to the
* argument array as-is, and to the property set after capitalizing it and adding "liquibase"
* to the front.
*/
def initializeGlobalArguments() {
allGlobalArguments = new HashSet<>()
allGlobalProperties = new HashSet<>()
// This is also how LiquibaseCommandLine.addGlobalArgs() gets global args.
SortedSet<ConfigurationDefinition<?>> globalConfigurations = Scope
.getCurrentScope()
.getSingleton(LiquibaseConfiguration.class)
.getRegisteredDefinitions(false);
globalConfigurations.each { opt ->
// fix it and add it.
def fixedArg = fixGlobalArgument(opt.getKey())
allGlobalArguments += fixedArg
allGlobalProperties += "liquibase" + fixedArg.capitalize()
opt.getAliasKeys().each {
def fixedAlias = fixGlobalArgument(it)
allGlobalArguments += fixedAlias
allGlobalProperties += "liquibase" + fixedAlias.capitalize()
}
}
}

/**
* Add a set of command arguments to our collection of known commands arguments. The plugin
* adds them for one command at a time as it creates tasks. The Argument Builder will then use
* this list to figure out what arguments are command arguments vs. global arguments.
*
* @param liquibaseCommand the command to add.
* @param commandArguments the arguments to add.
*/
def addCommand(CommandDefinition liquibaseCommand) {
allCommands += liquibaseCommand
def addCommandArguments(commandArguments) {
// Add this command's supported arguments to the set of overall command arguments.
commandArguments.each { argName ->
// We'll deal with changelogParameters in a special way later.
if ( argName == "changelogParameters" ) {
return
}
allCommandArguments += argName
allCommandProperties += "liquibase" + argName.capitalize()
}
}

/**
* Build arguments, in the right order, to pass to Liquibase.
* Build arguments, in the right order, to pass to Liquibase. Note that all the argument sets
* must have already been initialized. We can't ask Liquibase for anything because we'll be
* using a different classpath at execution time.
*
* @param activity the activity being run, which contains global and command parameters.
* @param liquibaseCommand the liquibase being run. This will be used to determine which
* command arguments are valid for the set of arguments we're building.
* @param commandName the name of the liquibase command being run.
* @param supportedCommandArguments the command arguments supported by the command being run.
* @return the argument string to pass to liquibase when we invoke it.
*/
def buildLiquibaseArgs(Activity activity, CommandDefinition liquibaseCommand) {
// Start by initializing our static option and property sets.
initializeCommandArguments()
initializeGlobalArguments()

def buildLiquibaseArgs(Activity activity, commandName, supportedCommandArguments) {
// This is what we'll ultimately return.
def liquibaseArgs = []

// Different parts of our liquibaseArgs before we string 'em all together.
def globalArgs = []
def commandArguments = []
def supportedCommandArguments = []
def sendingChangelog = false

// Build a list of all the arguments (and argument aliases) supported by the given command.
liquibaseCommand.getArguments().each { argName, a ->
supportedCommandArguments += a.name
// Starting with Liquibase 4.16, command arguments can have aliases
def supportsAliases = CommandArgumentDefinition.getDeclaredFields().find { it.name == "aliases" }
if ( supportsAliases ) {
supportedCommandArguments += a.aliases
}
}

// Create a merged map of activity arguments and arguments given as Gradle properties, then
// process each of the arguments from the map, figuring out what kind of argument each one
// is and responding accordingly.
Expand All @@ -103,7 +129,7 @@ class ArgumentBuilder {
// add the argument if it is the changeLogFile arg, and we're running the
// drop-all command.
if ( argumentName == 'changelogFile' ) {
if ( liquibaseCommand.name[0] == 'drop-all' ) {
if ( commandName == 'drop-all' ) {
return
}
// Still here? It's changelogFile, but not drop-all. Note that we will be
Expand All @@ -114,20 +140,20 @@ class ArgumentBuilder {
} else {
// If nothing matched above, then we had a command argument that was not supported
// by the command being run.
project.logger.debug("skipping the ${argumentName} command argument because it is not supported by the ${liquibaseCommand.name[0]}")
project.logger.debug("skipping the ${argumentName} command argument because it is not supported by the ${commandName} command")
}
}

// If we're processing the db-doc command, and we don't have an output directory in our
// command arguments, add it here. The db-doc command is the only one that has a default
// value.
if ( liquibaseCommand.name[0] == "dbDoc" && !commandArguments.any {it.startsWith("--output-directory") } ) {
if ( commandName == "dbDoc" && !commandArguments.any {it.startsWith("--output-directory") } ) {
commandArguments += "--output-directory=${project.buildDir}/database/docs"
}

// Now build our final argument array in the following order:
// global args, command, command args, changelog parameters (-D args)
liquibaseArgs = globalArgs + toKebab(liquibaseCommand.name[0]) + commandArguments
liquibaseArgs = globalArgs + toKebab(commandName) + commandArguments

// If we're sending a changelog, we need to also send change log parameters. Unfortunately,
// due to a bug in liquibase itself (https://liquibase.jira.com/browse/CORE-2519), we need
Expand All @@ -142,77 +168,6 @@ class ArgumentBuilder {
return liquibaseArgs
}

/**
* Initialize the command argument and command properties sets, if we haven't done it already.
* We do this only once for performance reasons.
* <p>
* This method loops through all the Liquibase commands, asking each for its supported
* arguments. Each one is added to the argument array as-is, and to the property set after
* capitalizing it and adding "liquibase" to the front.
*/
private initializeCommandArguments() {
// Have we done this already?
if ( allCommandArguments ) {
return
}

allCommandArguments = new HashSet<>()
allCommandProperties = new HashSet<>()

allCommands.each { command ->
// Add this command's supported arguments to the set of overall command arguments.
command.getArguments().each { argName, a ->
// We'll deal with changelogParameters in a special way later.
if ( argName == "changelogParameters" ) {
return
}
allCommandArguments += argName
allCommandProperties += "liquibase" + argName.capitalize()
def supportsAliases = CommandArgumentDefinition.getDeclaredFields().find { it.name == "aliases" }
if ( supportsAliases ) {
a.aliases.each { aliasName ->
allCommandArguments += aliasName
allCommandProperties += "liquibase" + aliasName.capitalize()
}
}
}
}
}

/**
* Initialize the global argument and global properties sets, if we haven't done it already.
* We do this only once for performance reasons.
* <p>
* This method asks Liquibase for all the supported global arguments. Each one is added to the
* argument array as-is, and to the property set after capitalizing it and adding "liquibase"
* to the front.
*/
private initializeGlobalArguments() {
// Have we done this already?
if ( allGlobalArguments ) {
return
}

allGlobalArguments = new HashSet<>()
allGlobalProperties = new HashSet<>()
// This is also how LiquibaseCommandLine.addGlobalArgs() gets global args.
SortedSet<ConfigurationDefinition<?>> globalConfigurations = Scope
.getCurrentScope()
.getSingleton(LiquibaseConfiguration.class)
.getRegisteredDefinitions(false);
globalConfigurations.each { opt ->
// fix it and add it.
def fixedArg = fixGlobalArgument(opt.getKey())
allGlobalArguments += fixedArg
allGlobalProperties += "liquibase" + fixedArg.capitalize()
opt.getAliasKeys().each {
def fixedAlias = fixGlobalArgument(it)
allGlobalArguments += fixedAlias
allGlobalProperties += "liquibase" + fixedAlias.capitalize()
}
}
}

/**
* Little helper method to "fix" a global argument. Many of the argument names, as Liquibase
* gives them to us, start with "liquibase.". We want to remove that prefix. We also want to
Expand Down
15 changes: 12 additions & 3 deletions src/main/groovy/org/liquibase/gradle/LiquibasePlugin.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
package org.liquibase.gradle

import liquibase.Scope
import liquibase.command.CommandArgumentDefinition
import liquibase.command.CommandDefinition
import liquibase.command.CommandFactory
import liquibase.configuration.ConfigurationDefinition
import liquibase.configuration.LiquibaseConfiguration
import org.gradle.api.Project
import org.gradle.api.Plugin

Expand Down Expand Up @@ -52,15 +55,20 @@ class LiquibasePlugin implements Plugin<Project> {
* @param project the project to enhance
*/
void applyTasks(Project project) {
// Make an argument builder for tasks to share.
// Make an argument builder for tasks to share, and initialize the global arguments while
// we are still in the apply phase.
ArgumentBuilder builder = new ArgumentBuilder(project: project)
builder.initializeGlobalArguments()

// Get the commands from the CommandFactory that are not internal, not hidden, and not the
// init command.
Set<CommandDefinition> commands = Scope.getCurrentScope().getSingleton(CommandFactory.class).getCommands(false)
def supportedCommands = commands.findAll { !it.hidden && !it.name.contains("init") }
supportedCommands.each { command ->
// Build a list of all the arguments (and argument aliases) supported by the given command.
def supportedCommandArguments = Util.argumentsForCommand(command)
// Let the builder know about the command so it can process arguments later
builder.addCommand(command)
builder.addCommandArguments(supportedCommandArguments)

// If the command has a nested command, append it to the task name.
def taskName = command.name[0]
Expand All @@ -75,7 +83,8 @@ class LiquibasePlugin implements Plugin<Project> {
project.tasks.register(taskName, LiquibaseTask) {
group = 'Liquibase'
description = command.shortDescription
liquibaseCommand = command
commandName = command.name[0]
commandArguments = supportedCommandArguments
argumentBuilder = builder
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/main/groovy/org/liquibase/gradle/LiquibaseTask.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ class LiquibaseTask extends JavaExec {

/** The Liquibase command to run */
@Input
CommandDefinition liquibaseCommand
def commandName

@Input
def commandArguments

/** The supported arguments of the command */


/** The argument builder that will build the arguments to sent to Liquiabse. */
@Input
Expand Down Expand Up @@ -81,7 +87,7 @@ class LiquibaseTask extends JavaExec {
*/
def runLiquibase(activity) {

def args = argumentBuilder.buildLiquibaseArgs(activity, liquibaseCommand)
def args = argumentBuilder.buildLiquibaseArgs(activity, commandName, commandArguments)
setArgs(args)

def classpath = project.configurations.getByName(LiquibasePlugin.LIQUIBASE_RUNTIME_CONFIGURATION)
Expand Down
23 changes: 23 additions & 0 deletions src/main/groovy/org/liquibase/gradle/Util.groovy
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.liquibase.gradle

import liquibase.command.CommandArgumentDefinition
import liquibase.command.CommandDefinition

/**
* Utility class to hold our helper methods.
*
Expand Down Expand Up @@ -34,4 +37,24 @@ class Util {
return givenVersions.size() > targetVersions.size()
}

/**
* Get the command arguments for a Liquibase command
* @param liquibaseCommand the Liquibase CommandDefinition whose arguments we need.
* @return an array of supported arguments.
*/
static def argumentsForCommand(CommandDefinition liquibaseCommand) {
// Build a list of all the arguments (and argument aliases) supported by the given command.
def supportedCommandArguments = []
liquibaseCommand.getArguments().each { argName, a ->
supportedCommandArguments += a.name
// Starting with Liquibase 4.16, command arguments can have aliases
def supportsAliases = CommandArgumentDefinition.getDeclaredFields().find { it.name == "aliases" }
if ( supportsAliases ) {
supportedCommandArguments += a.aliases
}
}
return supportedCommandArguments

}

}
Loading

0 comments on commit f9cb150

Please sign in to comment.