blob: 2080c5e4b4c5f22961b6f91f5e4b04c21fb97d09 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.codehaus.groovy.tools
import groovy.grape.Grape
import org.apache.ivy.util.DefaultMessageLogger
import org.apache.ivy.util.Message
import picocli.CommandLine
import picocli.CommandLine.Command
import picocli.CommandLine.Option
import picocli.CommandLine.Parameters
import picocli.CommandLine.ParentCommand
import picocli.CommandLine.Unmatched
@SuppressWarnings('Println')
@Command(name = 'grape', description = 'Allows for the inspection and management of the local grape cache.',
subcommands = [Install, Uninstall, ListCommand, Resolve, CommandLine.HelpCommand])
class GrapeMain implements Runnable {
@Option(names = ['-D', '--define'], description = 'define a system property', paramLabel = '<name=value>')
private final Map<String, String> properties = new LinkedHashMap<String, String>()
@SuppressWarnings('UnusedPrivateField') // used in run()
@Option(names = ['-r', '--resolver'], description = 'define a grab resolver (for install)', paramLabel = '<url>')
private final List<String> resolvers = new ArrayList<String>()
@Option(names = ['-q', '--quiet'], description = 'Log level 0 - only errors')
private boolean quiet
@Option(names = ['-w', '--warn'], description = 'Log level 1 - errors and warnings')
private boolean warn
@Option(names = ['-i', '--info'], description = 'Log level 2 - info')
private boolean info
@Option(names = ['-V', '--verbose'], description = 'Log level 3 - verbose')
private boolean verbose
@Option(names = ['-d', '--debug'], description = 'Log level 4 - debug')
private boolean debug
@Unmatched List<String> unmatched = new ArrayList<String>()
private CommandLine parser
static void main(String[] args) {
GrapeMain grape = new GrapeMain()
def parser = new CommandLine(grape)
parser.addMixin('helpOptions', new HelpOptionsMixin())
parser.subcommands.findAll { k, v -> k != 'help' }.each { k, v -> v.addMixin('helpOptions', new HelpOptionsMixin()) }
grape.parser = parser
parser.execute(args)
}
void run() {
if (unmatched) {
System.err.println "grape: '${unmatched[0]}' is not a grape command. See 'grape --help'"
} else {
parser.usage(System.out) // if no subcommand was specified
}
}
@SuppressWarnings('UnusedPrivateMethod') // used in run()
private void init() {
properties.each { k, v ->
System.setProperty(k, v)
}
}
@SuppressWarnings('UnusedPrivateMethod') // used in run()
private void setupLogging(int defaultLevel = 2) { // = Message.MSG_INFO -> some parsing error :(
if (quiet) {
Message.defaultLogger = new DefaultMessageLogger(Message.MSG_ERR)
} else if (warn) {
Message.defaultLogger = new DefaultMessageLogger(Message.MSG_WARN)
} else if (info) {
Message.defaultLogger = new DefaultMessageLogger(Message.MSG_INFO)
} else if (verbose) {
Message.defaultLogger = new DefaultMessageLogger(Message.MSG_VERBOSE)
} else if (debug) {
Message.defaultLogger = new DefaultMessageLogger(Message.MSG_DEBUG)
} else {
Message.defaultLogger = new DefaultMessageLogger(defaultLevel)
}
}
/**
* Defines help options (--help and --version) and a version provider used by the top-level command and all subcommands.
* Intended to be installed as a picocli mixin.
*/
// IMPLEMENTATION NOTE:
// The @Command(mixinStandardHelpOptions = true) attribute cannot be used because
// the unix standard short option for version help is uppercase -V, while previous versions
// of this class use lowercase -v. This custom mixin preserves option compatibility.
@Command(versionProvider = VersionProvider, sortOptions = false,
parameterListHeading = '%nParameters:%n',
optionListHeading = '%nOptions:%n',
descriptionHeading = '%n')
private static class HelpOptionsMixin {
@Option(names = ['-h', '--help'], usageHelp = true, description = 'usage information') boolean isHelpRequested
@Option(names = ['-v', '--version'], versionHelp = true, description = 'display the Groovy and JVM versions') boolean isVersionRequested
}
private static class VersionProvider implements CommandLine.IVersionProvider {
String[] getVersion() {
String version = GroovySystem.version
["Groovy Version: $version JVM: ${System.getProperty('java.version')}"]
}
}
@Command(name = 'install', header = 'Installs a particular grape',
description = 'Installs the specified groovy module or maven artifact. If a version is specified that specific version will be installed, otherwise the most recent version will be used (as if `*` was passed in).')
private static class Install implements Runnable {
@Parameters(index = '0', arity = '1', description = 'Which module group the module comes from. Translates directly to a Maven groupId or an Ivy Organization. Any group matching /groovy[x][\\..*]^/ is reserved and may have special meaning to the groovy endorsed modules.')
String group
@Parameters(index = '1', arity = '1', description = 'The name of the module to load. Translated directly to a Maven artifactId or an Ivy artifact.')
String module
@Parameters(index = '2', arity = '0..1', description = 'The version of the module to use. Either a literal version `1.1-RC3` or an Ivy Range `[2.2.1,)` meaning 2.2.1 or any greater version).')
String version = '*'
@Parameters(index = '3', arity = '0..1', description = 'The optional classifier to use (for example, jdk15).')
String classifier
@ParentCommand GrapeMain parentCommand
void run() {
parentCommand.init()
// set the instance so we can re-set the logger
Grape.instance
parentCommand.setupLogging()
parentCommand.resolvers.each { String url ->
Grape.addResolver(name:url, root:url)
}
try {
Grape.grab(autoDownload: true, group: group, module: module, version: version, classifier: classifier, noExceptions: true)
} catch (Exception ex) {
System.err.println "An error occurred : $ex"
}
}
}
@Command(name = 'list', header = 'Lists all installed grapes',
description = 'Lists locally installed modules (with their full maven name in the case of groovy modules) and versions.')
private static class ListCommand implements Runnable {
@ParentCommand GrapeMain parentCommand
void run() {
parentCommand.init()
println ''
int moduleCount = 0
int versionCount = 0
// set the instance so we can re-set the logger
Grape.instance
parentCommand.setupLogging()
Grape.enumerateGrapes().each {String groupName, Map group ->
group.each {String moduleName, List<String> versions ->
println "$groupName $moduleName $versions"
moduleCount++
versionCount += versions.size()
}
}
println ''
println "$moduleCount Grape modules cached"
println "$versionCount Grape module versions cached"
}
}
@Command(name = 'resolve', header = 'Enumerates the jars used by a grape',
customSynopsis = 'grape resolve [-adhisv] (<groupId> <artifactId> <version>)+',
description = [
'Prints the file locations of the jars representing the artifacts for the specified module(s) and the respective transitive dependencies.',
'',
'Parameters:',
' <group> Which module group the module comes from. Translates directly',
' to a Maven groupId or an Ivy Organization. Any group',
' matching /groovy[x][\\..*]^/ is reserved and may have',
' special meaning to the groovy endorsed modules.',
' <module> The name of the module to load. Translated directly to a',
' Maven artifactId or an Ivy artifact.',
' <version> The version of the module to use. Either a literal version',
' `1.1-RC3` or an Ivy Range `[2.2.1,)` meaning 2.2.1 or any',
' greater version).'
])
private static class Resolve implements Runnable {
@Option(names = ['-a', '--ant'], description = 'Express dependencies in a format applicable for an ant script')
private boolean ant
@Option(names = ['-d', '--dos'], description = 'Express dependencies in a format applicable for a windows batch file')
private boolean dos
@Option(names = ['-s', '--shell'], description = 'Express dependencies in a format applicable for a unix shell script')
private boolean shell
@Option(names = ['-i', '--ivy'], description = 'Express dependencies in an ivy-like format')
private boolean ivyFormatRequested
@Parameters(hidden = true) // parameter description is embedded in the command description
List<String> args = new ArrayList<>() // the positional parameters
@ParentCommand GrapeMain parentCommand
void run() {
parentCommand.init()
// set the instance so we can re-set the logger
Grape.instance
parentCommand.setupLogging(Message.MSG_ERR)
if ((args.size() % 3) != 0) {
println 'There needs to be a multiple of three arguments: (group module version)+'
return
}
if (args.size() < 3) {
println 'At least one Grape reference is required'
return
}
def before, between, after
if (ant) {
before = '<pathelement location="'
between = '">\n<pathelement location="'
after = '">'
} else if (dos) {
before = 'set CLASSPATH='
between = ';'
after = ''
} else if (shell) {
before = 'export CLASSPATH='
between = ':'
after = ''
} else if (ivyFormatRequested) {
before = '<dependency '
between = '">\n<dependency '
after = '">'
} else {
before = ''
between = '\n'
after = '\n'
}
def iter = args.iterator()
def params = [[:]]
def depsInfo = [] // this list will contain the module/group/version info of all resolved dependencies
if (ivyFormatRequested) {
params << depsInfo
}
while (iter.hasNext()) {
params.add([group: iter.next(), module: iter.next(), version: iter.next()])
}
try {
def results = []
def uris = Grape.resolve(*params)
if (!ivyFormatRequested) {
for (URI uri: uris) {
if (uri.scheme == 'file') {
results += new File(uri).path
} else {
results += uri.toASCIIString()
}
}
} else {
depsInfo.each { dep ->
results += ('org="' + dep.group + '" name="' + dep.module + '" revision="' + dep.revision)
}
}
if (results) {
println "${before}${results.join(between)}${after}"
} else {
println 'Nothing was resolved'
}
} catch (Exception e) {
System.err.println "Error in resolve:\n\t$e.message"
if (e.message =~ /unresolved dependency/) println 'Perhaps the grape is not installed?'
}
}
}
@Command(name = 'uninstall',
description = 'Uninstalls a particular grape (non-transitively removes the respective jar file from the grape cache).')
private static class Uninstall implements Runnable {
@Parameters(index = '0', arity = '1', description = 'Which module group the module comes from. Translates directly to a Maven groupId or an Ivy Organization. Any group matching /groovy[x][\\..*]^/ is reserved and may have special meaning to the groovy endorsed modules.')
String group
@Parameters(index = '1', arity = '1', description = 'The name of the module to load. Translated directly to a Maven artifactId or an Ivy artifact.')
String module
@Parameters(index = '2', arity = '1', description = 'The version of the module to use. Either a literal version `1.1-RC3` or an Ivy Range `[2.2.1,)` meaning 2.2.1 or any greater version).')
String version
// TODO make version optional? support classifier?
//@Parameters(index = '3', arity = '0..1', description = 'The optional classifier to use (for example, jdk15).')
//String classifier;
@ParentCommand GrapeMain parentCommand
void run() {
parentCommand.init()
// set the instance so we can re-set the logger
Grape.instance
parentCommand.setupLogging()
if (!Grape.enumerateGrapes().find {String groupName, Map g ->
g.any {String moduleName, List<String> versions ->
group == groupName && module == moduleName && version in versions
}
}) {
println "uninstall did not find grape matching: $group $module $version"
def fuzzyMatches = Grape.enumerateGrapes().findAll { String groupName, Map g ->
g.any {String moduleName, List<String> versions ->
groupName.contains(group) || moduleName.contains(module) ||
group.contains(groupName) || module.contains(moduleName)
}
}
if (fuzzyMatches) {
println 'possible matches:'
fuzzyMatches.each { String groupName, Map g -> println " $groupName: $g" }
}
return
}
Grape.instance.uninstallArtifact(group, module, version)
}
}
}