diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java index 81e1110299..5a9706a633 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java @@ -739,6 +739,7 @@ boolean hasExpired(Node node, long now) { * @param node the entry to evict * @param cause the reason to evict * @param now the current time, used only if expiring + * @return if the entry was evicted */ @GuardedBy("evictionLock") @SuppressWarnings({"PMD.CollapsibleIfStatements", "GuardedByChecker"}) @@ -864,7 +865,7 @@ void refreshIfNeeded(Node node, long now) { K key; V oldValue; long oldWriteTime = node.getWriteTime(); - long refreshWriteTime = isAsync ? (now + Async.MAXIMUM_EXPIRY) : now; + long refreshWriteTime = (now + Async.MAXIMUM_EXPIRY); if (((now - oldWriteTime) > refreshAfterWriteNanos()) && ((key = node.getKey()) != null) && ((oldValue = node.getValue()) != null) && node.casWriteTime(oldWriteTime, refreshWriteTime)) { diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Caffeine.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Caffeine.java index 05e31e2edb..c846422567 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Caffeine.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Caffeine.java @@ -20,6 +20,8 @@ import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.util.ConcurrentModificationException; +import java.util.IdentityHashMap; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; @@ -411,7 +413,9 @@ Weigher getWeigher(boolean isAsync) { * {@link WeakReference} (by default, strong references are used). *

* Warning: when this method is used, the resulting cache will use identity ({@code ==}) - * comparison to determine equality of keys. + * comparison to determine equality of keys. Its {@link Cache#asMap} view will therefore + * technically violate the {@link Map} specification (in the same way that {@link IdentityHashMap} + * does). *

* Entries with keys that have been garbage collected may be counted in * {@link Cache#estimatedSize()}, but will never be visible to read or write operations; such diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java index 393d0471da..c781d72d8f 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java @@ -26,8 +26,10 @@ import static org.hamcrest.Matchers.both; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; @@ -36,8 +38,10 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import org.testng.annotations.Listeners; @@ -60,6 +64,7 @@ import com.github.benmanes.caffeine.cache.testing.CheckNoWriter; import com.github.benmanes.caffeine.cache.testing.RefreshAfterWrite; import com.github.benmanes.caffeine.cache.testing.RemovalNotification; +import com.github.benmanes.caffeine.cache.testing.TrackingExecutor; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -206,6 +211,45 @@ public void get_sameFuture(CacheContext context) { await().until(() -> cache.synchronous().getIfPresent(key), is(-key)); } + @Test(dataProvider = "caches") + @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, + refreshAfterWrite = Expire.ONE_MINUTE, executor = CacheExecutor.THREADED) + public void get_slowRefresh(CacheContext context) { + Integer key = context.absentKey(); + Integer originalValue = context.absentValue(); + AtomicBoolean reloaded = new AtomicBoolean(); + AtomicInteger reloading = new AtomicInteger(); + ThreadPoolExecutor executor = (ThreadPoolExecutor) + ((TrackingExecutor) context.executor()).delegate(); + LoadingCache cache = context.build(new CacheLoader() { + @Override public Integer load(Integer key) { + throw new AssertionError(); + } + @Override public Integer reload(Integer key, Integer oldValue) { + int count = reloading.incrementAndGet(); + await().untilTrue(reloaded); + return count; + } + }); + + cache.put(key, originalValue); + + context.ticker().advance(2, TimeUnit.MINUTES); + assertThat(cache.get(key), is(originalValue)); + + await().untilAtomic(reloading, is(1)); + assertThat(cache.getIfPresent(key), is(originalValue)); + + context.ticker().advance(2, TimeUnit.MINUTES); + assertThat(cache.get(key), is(originalValue)); + + reloaded.set(true); + await().until(() -> cache.get(key), is(not(originalValue))); + await().until(executor::getQueue, is(empty())); + assertThat(reloading.get(), is(1)); + assertThat(cache.get(key), is(1)); + } + @Test(dataProvider = "caches") @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.NULL) public void get_null(AsyncLoadingCache cache, CacheContext context) { diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/TrackingExecutor.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/TrackingExecutor.java index 5e1abd8308..3ed82ca245 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/TrackingExecutor.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/TrackingExecutor.java @@ -29,12 +29,10 @@ */ public final class TrackingExecutor extends ForwardingExecutorService { private final ExecutorService delegate; - private final AtomicInteger totalTasks; private final AtomicInteger failures; public TrackingExecutor(ExecutorService executor) { delegate = requireNonNull(executor); - totalTasks = new AtomicInteger(); failures = new AtomicInteger(); } @@ -55,16 +53,12 @@ public void execute(Runnable command) { } } - public int totalTasksCount() { - return totalTasks.get(); - } - public int failureCount() { return failures.get(); } @Override - protected ExecutorService delegate() { + public ExecutorService delegate() { return delegate; } } diff --git a/config/findbugs/exclude.xml b/config/findbugs/exclude.xml index 1b4cf14d51..7380d6be8c 100644 --- a/config/findbugs/exclude.xml +++ b/config/findbugs/exclude.xml @@ -27,6 +27,11 @@ + + + + + diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 216e60d1ae..afa04229da 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -38,7 +38,7 @@ ext { jsr305: '3.0.2', jsr330: '1', stream: '2.9.5', - univocityParsers: '2.4.1', + univocityParsers: '2.5.1', ycsb: '0.12.0', xz: '1.6', ] @@ -51,7 +51,7 @@ ext { junit: '4.12', mockito: '2.8.47', paxExam: '4.11.0', - testng: '6.11', + testng: '6.12', truth: '0.24', ] benchmarkVersions = [ @@ -62,33 +62,33 @@ ext { ehcache3: '3.3.1', elasticSearch: '5.4.0', expiringMap: '0.5.8', - jackrabbit: '1.7.3', - jamm: '0.3.1', + jackrabbit: '1.7.5', + jamm: '0.3.2', javaObjectLayout: '0.8', jmh: 1.19, koloboke: '0.6.8', ohc: '0.6.1', - rapidoid: '5.3.5', + rapidoid: '5.4.0', slf4j: '1.7.25', tcache: '1.0.3', ] pluginVersions = [ buildscan: '1.8', buildscanRecipes: '0.2.0', - checkstyle: '8.0', + checkstyle: '8.1', coveralls: '2.8.1', coverity: '1.0.10', errorProne: '0.0.10', jacoco: '0.7.9', jmh: '0.4.2', - jmhReport: '0.4.1', + jmhReport: '0.5.0', nexus: '2.3.1', pmd: '5.8.1', propdeps: '0.0.10.RELEASE', semanticVersioning: '1.1.0', shadow: '2.0.1', sonarqube: '2.5', - spotbugs: '1.0', + spotbugs: '1.2', stats: '0.2.0', versions: '0.15.0', ] @@ -130,7 +130,7 @@ ext { exclude group: 'org.hamcrest' }, osgiCompile: [ - 'org.apache.felix:org.apache.felix.framework:5.6.4', + 'org.apache.felix:org.apache.felix.framework:5.6.6', "org.ops4j.pax.exam:pax-exam-junit4:${testVersions.paxExam}", ], osgiRuntime: [ diff --git a/gradle/gradle-completion.bash b/gradle/gradle-completion.bash index f0347be136..d3ce6eb597 100644 --- a/gradle/gradle-completion.bash +++ b/gradle/gradle-completion.bash @@ -2,29 +2,66 @@ # Avoid inaccurate completions for subproject tasks COMP_WORDBREAKS=$(echo "$COMP_WORDBREAKS" | sed -e 's/://g') -_gradle() -{ - local cur=${COMP_WORDS[COMP_CWORD]} - local args - # Use Gradle wrapper when it exists. - local gradle_cmd='gradle' - if [[ -x ./gradlew ]]; then - gradle_cmd='./gradlew' - fi +__gradle-set-project-root-dir() { + local dir=`pwd` + project_root_dir=`pwd` + while [[ $dir != '/' ]]; do + if [[ -f "$dir/settings.gradle" || -f "$dir/gradlew" ]]; then + project_root_dir=$dir + return 0 + fi + dir="$(dirname "$dir")" + done + return 1 +} - local cache_dir="$HOME/.gradle/completion" +__gradle-init-cache-dir() { + cache_dir="$HOME/.gradle/completion" mkdir -p $cache_dir +} + +__gradle-set-build-file() { + # Look for default build script in the settings file (settings.gradle by default) + # Otherwise, default is the file 'build.gradle' in the current directory. + gradle_build_file="$project_root_dir/build.gradle" + if [[ -f "$project_root_dir/settings.gradle" ]]; then + local build_file_name=$(grep "^rootProject\.buildFileName" "$project_root_dir/settings.gradle" | \ + sed -n -e "s/rootProject\.buildFileName = [\'\"]\(.*\)[\'\"]/\1/p") + gradle_build_file="$project_root_dir/${build_file_name:-build.gradle}" + fi +} + +__gradle-set-cache-name() { + # Cache name is constructed from the absolute path of the build file. + cache_name=$(echo $gradle_build_file | sed -e 's/\//_/g') +} + +__gradle-set-files-checksum() { + # Cache MD5 sum of all Gradle scripts and modified timestamps + if builtin command -v md5 > /dev/null; then + gradle_files_checksum=$(md5 -q -s "$(cat "$cache_dir/$cache_name" | xargs ls -o 2>/dev/null)") + elif builtin command -v md5sum > /dev/null; then + gradle_files_checksum=$(cat "$cache_dir/$cache_name" | xargs ls -o 2>/dev/null | md5sum | awk '{print $1}') + else + echo "Cannot generate completions as neither md5 nor md5sum exist on \$PATH" + fi +} + +__gradle-generate-script-cache() { # Invalidate cache after 3 weeks by default local cache_ttl_mins=${GRADLE_CACHE_TTL_MINUTES:-30240} local script_exclude_pattern=${GRADLE_COMPLETION_EXCLUDE_PATTERN:-"/(build|integTest|out)/"} - # Set bash internal field separator to '\n' - # This allows us to provide descriptions for options and tasks - local OLDIFS="$IFS" - local IFS=$'\n' + if [[ ! $(find $cache_dir/$cache_name -mmin -$cache_ttl_mins 2>/dev/null) ]]; then + # Cache all Gradle scripts + local gradle_build_scripts=$(find $project_root_dir -type f -name "*.gradle" -o -name "*.gradle.kts" 2>/dev/null | egrep -v "$script_exclude_pattern") + printf "%s\n" "${gradle_build_scripts[@]}" > $cache_dir/$cache_name + fi +} - if [[ ${cur} == --* ]]; then - args="--build-file - Specifies the build file +__gradle-long-options() { + local args="--build-cache - Enables the Gradle build cache +--build-file - Specifies the build file --configure-on-demand - Only relevant projects are configured --console - Type of console output to generate (plain auto rich) --continue - Continues task execution after a task failure @@ -41,8 +78,10 @@ _gradle() --info - Set log level to INFO --init-script - Specifies an initialization script --max-workers - Set the maximum number of workers that Gradle may use +--no-build-cache - Do not use the Gradle build cache --no-daemon - Do not use the Gradle Daemon --no-rebuild - Do not rebuild project dependencies +--no-scan - Do not create a build scan --no-search-upwards - Do not search in parent directories for a settings.gradle --offline - Build without accessing network resources --parallel - Build projects in parallel @@ -53,26 +92,36 @@ _gradle() --quiet - Log errors only --recompile-scripts - Forces scripts to be recompiled, bypassing caching --refresh-dependencies - Refresh the state of dependencies ---rerun-task - Specifies that any task optimization is ignored +--rerun-tasks - Specifies that any task optimization is ignored +--scan - Create a build scan --settings-file - Specifies the settings file --stacktrace - Print out the stacktrace also for user exceptions --status - Print Gradle Daemon status --stop - Stop all Gradle Daemons --system-prop - Set a system property ---version - Prints Gradle version info" - COMPREPLY=( $(compgen -W "$args" -- "$cur") ) - elif [[ ${cur} == -D* ]]; then - args="-Dorg.gradle.cache.reserved.mb= - Reserve Gradle Daemon memory for operations +--version - Prints Gradle version info +--warn - Log warnings and errors only" + COMPREPLY=( $(compgen -W "$args" -- "${COMP_WORDS[COMP_CWORD]}") ) +} + +__gradle-properties() { + local args="-Dorg.gradle.cache.reserved.mb= - Reserve Gradle Daemon memory for operations +-Dorg.gradle.caching= - Set true to enable Gradle build cache -Dorg.gradle.daemon.debug= - Set true to debug Gradle Daemon -Dorg.gradle.daemon.idletimeout= - Kill Gradle Daemon after # idle millis -Dorg.gradle.debug= - Set true to debug Gradle Client -Dorg.gradle.jvmargs= - Set JVM arguments -Dorg.gradle.java.home= - Set JDK home dir +-Dorg.gradle.logging.level= - Set default Gradle log level (quiet warn lifecycle info debug) -Dorg.gradle.parallel= - Set true to enable parallel project builds (incubating) --Dorg.gradle.parallel.intra= - Set true to enable intra-project parallel builds (incubating)" - COMPREPLY=( $(compgen -W "$args" -- "$cur") ) - elif [[ ${cur} == -* ]]; then - args="-? - Shows a help message +-Dorg.gradle.parallel.intra= - Set true to enable intra-project parallel builds (incubating) +-Dorg.gradle.workers.max= - Set the number of workers Gradle is allowed to use" + COMPREPLY=( $(compgen -W "$args" -- "${COMP_WORDS[COMP_CWORD]}") ) + return 0 +} + +__gradle-short-options() { + local args="-? - Shows a help message -a - Do not rebuild project dependencies -b - Specifies the build file -c - Specifies the settings file @@ -87,107 +136,150 @@ _gradle() -t - Continuous mode. Automatically re-run build after changes -u - Do not search in parent directories for a settings.gradle -v - Prints Gradle version info +-w - Log warnings and errors only -x - Specify a task to be excluded -D - Set a system property -I - Specifies an initialization script -P - Sets a project property of the root project -S - Print out the full (very verbose) stacktrace" - COMPREPLY=( $(compgen -W "$args" -- "$cur") ) - else - # Look for default build script in the settings file (settings.gradle by default) - # Otherwise, default is the file 'build.gradle' in the current directory. - local gradle_buildfile=build.gradle - if [[ -f settings.gradle ]]; then - local build_file_name=$(grep "^rootProject\.buildFileName" settings.gradle | \ - sed -n -e "s/rootProject\.buildFileName = [\'\"]\(.*\)[\'\"]/\1/p") - gradle_buildfile=${build_file_name:-build.gradle} - fi + COMPREPLY=( $(compgen -W "$args" -- "${COMP_WORDS[COMP_CWORD]}") ) +} - local gradle_files_checksum - # If we're in a Gradle project, check if completion cache is up-to-date - if [[ -f $gradle_buildfile ]]; then - # Cache name is constructed from the absolute path of the build file. - local cache_name=$(echo $(pwd)/$gradle_buildfile | sed -e 's/\//_/g') - if [[ ! $(find $cache_dir/$cache_name -mmin -$cache_ttl_mins 2>/dev/null) ]]; then - # Cache all Gradle scripts - local gradle_build_scripts=$(find . -type f -name "*.gradle" -o -name "*.gradle.kts" 2>/dev/null | egrep -v "$script_exclude_pattern") - printf "%s\n" "${gradle_build_scripts[@]}" > $cache_dir/$cache_name - fi +__gradle-notify-tasks-cache-build() { + # Notify user of cache rebuild + echo -e " (Building completion cache. Please wait)\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\c" + __gradle-generate-tasks-cache + # Remove "please wait" message by writing a bunch of spaces then moving back to the left + echo -e " \b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\c" +} + +__gradle-generate-tasks-cache() { + __gradle-set-files-checksum + + # Use Gradle wrapper when it exists. + local gradle_cmd="gradle" + if [[ -x "$project_root_dir/gradlew" ]]; then + gradle_cmd="$project_root_dir/gradlew" + fi - # Cache MD5 sum of all Gradle scripts and modified timestamps - if builtin command -v md5 > /dev/null; then - gradle_files_checksum=$(md5 -q -s "$(cat "$cache_dir/$cache_name" | xargs ls -o 2>/dev/null)") - elif builtin command -v md5sum > /dev/null; then - gradle_files_checksum=$(cat "$cache_dir/$cache_name" | xargs ls -o 2>/dev/null | md5sum | awk '{print $1}') + # Run gradle to retrieve possible tasks and cache. + # Reuse Gradle Daemon if IDLE but don't start a new one. + local gradle_tasks_output + if [[ ! -z "$($gradle_cmd --status 2>/dev/null | grep IDLE)" ]]; then + gradle_tasks_output="$($gradle_cmd -b $gradle_build_file --daemon -q tasks --all)" + else + gradle_tasks_output="$($gradle_cmd -b $gradle_build_file --no-daemon -q tasks --all)" + fi + local output_line + local task_description + local -a gradle_all_tasks=() + local -a root_tasks=() + local -a subproject_tasks=() + for output_line in $gradle_tasks_output; do + if [[ $output_line =~ ^([[:lower:]][[:alnum:][:punct:]]*)([[:space:]]-[[:space:]]([[:print:]]*))? ]]; then + task_name="${BASH_REMATCH[1]}" + task_description="${BASH_REMATCH[3]}" + gradle_all_tasks+=( "$task_name - $task_description" ) + # Completion for subproject tasks with ':' prefix + if [[ $task_name =~ ^([[:alnum:][:punct:]]+):([[:alnum:]]+) ]]; then + gradle_all_tasks+=( ":$task_name - $task_description" ) + subproject_tasks+=( "${BASH_REMATCH[2]}" ) else - echo "Cannot generate completions as neither md5 nor md5sum exist on \$PATH" - return 1 + root_tasks+=( "$task_name" ) fi + fi + done - if [[ ! -f $cache_dir/$cache_name.md5 || $gradle_files_checksum != "$(cat $cache_dir/$cache_name.md5)" || ! -f $cache_dir/$gradle_files_checksum ]]; then - # Notify user of cache rebuild - echo -e " (Building completion cache. Please wait)\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\c" + # subproject tasks can be referenced implicitly from root project + if [[ $GRADLE_COMPLETION_UNQUALIFIED_TASKS == "true" ]]; then + local -a implicit_tasks=() + implicit_tasks=( $(comm -23 <(printf "%s\n" "${subproject_tasks[@]}" | sort) <(printf "%s\n" "${root_tasks[@]}" | sort)) ) + for task in $(printf "%s\n" "${implicit_tasks[@]}"); do + gradle_all_tasks+=( $task ) + done + fi - # Run gradle to retrieve possible tasks and cache. - # Reuse Gradle Daemon if IDLE but don't start a new one. - local gradle_tasks_output - if [[ ! -z "$($gradle_cmd --status 2>/dev/null | grep IDLE)" ]]; then - gradle_tasks_output="$($gradle_cmd --daemon -q tasks --all)" - else - gradle_tasks_output="$($gradle_cmd --no-daemon -q tasks --all)" - fi - local output_line - local task_description - local -a gradle_all_tasks=() - local -a root_tasks=() - local -a subproject_tasks=() - for output_line in $gradle_tasks_output; do - if [[ $output_line =~ ^([[:lower:]][[:alnum:][:punct:]]*)([[:space:]]-[[:space:]]([[:print:]]*))? ]]; then - task_name="${BASH_REMATCH[1]}" - task_description="${BASH_REMATCH[3]}" - gradle_all_tasks+=( "$task_name - $task_description" ) - # Completion for subproject tasks with ':' prefix - if [[ $task_name =~ ^([[:alnum:]:]+):([[:alnum:]]+) ]]; then - gradle_all_tasks+=( ":$task_name - $task_description" ) - subproject_tasks+=( "${BASH_REMATCH[2]}" ) - else - root_tasks+=( "$task_name" ) - fi - fi - done - - # subproject tasks can be referenced implicitly from root project - if [[ $GRADLE_COMPLETION_UNQUALIFIED_TASKS == "true" ]]; then - local -a implicit_tasks=() - implicit_tasks=( $(comm -23 <(printf "%s\n" "${subproject_tasks[@]}" | sort) <(printf "%s\n" "${root_tasks[@]}" | sort)) ) - for task in $(printf "%s\n" "${implicit_tasks[@]}"); do - gradle_all_tasks+=( $task ) - done - fi + printf "%s\n" "${gradle_all_tasks[@]}" > $cache_dir/$gradle_files_checksum + echo $gradle_files_checksum > $cache_dir/$cache_name.md5 +} - printf "%s\n" "${gradle_all_tasks[@]}" > $cache_dir/$gradle_files_checksum - echo $gradle_files_checksum > $cache_dir/$cache_name.md5 +__gradle-completion-init() { + local cache_dir cache_name gradle_build_file gradle_files_checksum project_root_dir - # Remove "please wait" message by writing a bunch of spaces then moving back to the left - echo -e " \b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\c" - fi - else - return 1 - fi + local OLDIFS="$IFS" + local IFS=$'\n' + + __gradle-init-cache-dir + __gradle-set-project-root-dir + __gradle-set-build-file + if [[ -f $gradle_build_file ]]; then + __gradle-set-cache-name + __gradle-generate-script-cache + __gradle-set-files-checksum + __gradle-notify-tasks-cache-build + fi + + IFS="$OLDIFS" - if [[ -f $cache_dir/$gradle_files_checksum ]]; then - # Optimize here - this is the slowest part of completion - local -a cached_tasks - if [[ -z $cur ]]; then - cached_tasks=( $(cat $cache_dir/$gradle_files_checksum) ) + return 0 +} + +_gradle() { + local cache_dir cache_name gradle_build_file gradle_files_checksum project_root_dir + local cur=${COMP_WORDS[COMP_CWORD]} + # Set bash internal field separator to '\n' + # This allows us to provide descriptions for options and tasks + local OLDIFS="$IFS" + local IFS=$'\n' + + if [[ ${cur} == --* ]]; then + __gradle-long-options + elif [[ ${cur} == -D* ]]; then + __gradle-properties + elif [[ ${cur} == -* ]]; then + __gradle-short-options + else + __gradle-init-cache-dir + __gradle-set-project-root-dir + __gradle-set-build-file + if [[ -f $gradle_build_file ]]; then + __gradle-set-cache-name + __gradle-generate-script-cache + __gradle-set-files-checksum + + # The cache key is md5 sum of all gradle scripts, so it's valid if it exists. + if [[ -f $cache_dir/$cache_name.md5 ]]; then + local cached_checksum="$(cat $cache_dir/$cache_name.md5)" + local -a cached_tasks + if [[ -z $cur ]]; then + cached_tasks=( $(cat $cache_dir/$cached_checksum) ) + else + cached_tasks=( $(grep "^$cur" $cache_dir/$cached_checksum) ) + fi + COMPREPLY=( $(compgen -W "${cached_tasks[*]}" -- "$cur") ) else - cached_tasks=( $(grep "^$cur" $cache_dir/$gradle_files_checksum) ) + __gradle-notify-tasks-cache-build + fi + + # Regenerate tasks cache in the background + if [[ $gradle_files_checksum != "$(cat $cache_dir/$cache_name.md5)" || ! -f $cache_dir/$gradle_files_checksum ]]; then + $(__gradle-generate-tasks-cache 1>&2 2>/dev/null &) fi - COMPREPLY=( $(compgen -W "${cached_tasks[*]}" -- "$cur") ) else - echo "D'oh! There's a bug in the completion script, please submit an issue to eriwen/gradle-completion" - # previous steps failed, maybe cache dir is not readable/writable - return 1 + # Default tasks available outside Gradle projects + local args="buildEnvironment - Displays all buildscript dependencies declared in root project. +components - Displays the components produced by root project. +dependencies - Displays all dependencies declared in root project. +dependencyInsight - Displays the insight into a specific dependency in root project. +dependentComponents - Displays the dependent components of components in root project. +help - Displays a help message. +init - Initializes a new Gradle build. +model - Displays the configuration model of root project. +projects - Displays the sub-projects of root project. +properties - Displays the properties of root project. +tasks - Displays the tasks runnable from root project. +wrapper - Generates Gradle wrapper files." + COMPREPLY=( $(compgen -W "$args" -- "${COMP_WORDS[COMP_CWORD]}") ) fi fi @@ -201,9 +293,12 @@ _gradle() return 0 } complete -F _gradle gradle +complete -F _gradle gradle.bat complete -F _gradle gradlew +complete -F _gradle gradlew.bat complete -F _gradle ./gradlew +complete -F _gradle ./gradlew.bat -if hash gw 2>/dev/null; then +if hash gw 2>/dev/null || alias gw >/dev/null 2>&1; then complete -F _gradle gw -fi \ No newline at end of file +fi