diff --git a/README.md b/README.md index de13f92..de36296 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ # Gradle Natives Plugin -A Gradle plugin to aid in working with Java-based project that provide supporting native libraries. - -> ⚠️ Recently, JOGL and LWJGL have changed the layouts of their jars with respect to where the native libraries are located. This causes the plugin to stop working for those libraries. The way the plugin resolves the native libraries will need to be addressed and refactored. I don't really use this plugin, nor do I have a lot of time to work on it... so unless someone wants to create a pull request, it might be a while. If you use this plugin and this issue will cause problems for you, feel free to comment on [#15](https://github.com/cjstehno/gradle-natives/issues/15) to that effect - if there is enough interest, I will feel bad and fix it sooner. :-) +A Gradle plugin to aid in working with Java-based projects that provide supporting native libraries. ## Build -`gradlew clean build` + ./gradlew clean build ## Installation @@ -20,7 +18,7 @@ buildscript { } } dependencies { - classpath "gradle.plugin.com.stehno:gradle-natives:0.2.4" + classpath "gradle.plugin.com.stehno:gradle-natives:0.3.0" } } @@ -31,7 +29,7 @@ Alternately, you can use the new plug definition block in Gradle 2.1 and beyond. ```groovy plugins { - id 'com.stehno.natives' version '0.2.4' + id 'com.stehno.natives' version '0.3.0' } ``` @@ -39,36 +37,43 @@ The plugin is compiled on Java 7. ## Usage -To do anything useful with it, you need to configure it using the `natives` extension configuration, for example: +Without any additional configuration, the plugin will find all native libraries in all `compile` and `runtime` dependency configurations, for all platforms, and unpack them into +the `build/natives` directory of your project. You can configure this behavior by adding a `natives` block to your `build.gradle` file. The default behavior has the following configuration: ```groovy natives { - jars = [ 'lwjgl-platform-2.9.1-natives-windows' ] - platforms = 'windows' + configurations = ['compile', 'runtime'] + platforms = Platform.all() + outputDir = 'natives' } ``` -Which will find the specified jar in the project and extract the `.dll` files contained in it when the `unpackNatives` task is executed. +A `libraries` Closure may also be added to filter the resolved libraries, such as: -The `natives.jars` property accepts a single string or collection of strings representing names of jar files configured -on the project classpath (from other dependencies). If the string does not end with ".jar" the extension will be added. +```groovy +natives { + configurations = ['compile', 'runtime'] + platforms = Platform.all() + outputDir = 'natives' + libraries { + exclude = ['somelib.dll'] + } +} +``` -The `platforms` property accepts a single string or single Platform enum value, as well as a collection of either (or both mixed). If no platforms -are specified (value left null), all supported platforms will be assumed. +There are two tasks provided by the plguin: -Then to add the native libraries to the build, simply run: +* `listNatives` - lists all of the native libraries resolved by the current configuration. +* `includeNatives` - includes (copies) the resolved native libraries into the configured output directory. -``` -gradlew unpackNatives -``` +## Warning -Which will add the native libraries to the build under the directory `build/natives/PLATFORM` (where PLATFORM is the name -of the configured platform). +This plugin only resolves native libraries that are on the project classpath as dependencies of the project (Gradle dependencies, either direct or transitive). ## References -* http://cjstehno.github.io/gradle-natives -* https://github.com/cjstehno/coffeaelectronica/wiki/Going-Native-with-Gradle +* Site: http://cjstehno.github.io/gradle-natives +* Blog Post: http://coffeaelectronica.com/blog/2014/going-native-with-gradle.html [![Build Status](https://drone.io/github.com/cjstehno/gradle-natives/status.png)](https://drone.io/github.com/cjstehno/gradle-natives/latest) diff --git a/build.gradle b/build.gradle index 7f4c884..acc6c7f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,52 +1,40 @@ -/* - * Copyright (c) 2014 Christopher J. Stehno - * - * Licensed 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. - */ - plugins { - id 'com.gradle.plugin-publish' version '0.9.0' + id 'groovy' + id 'com.github.hierynomus.license' version '0.13.1' + id 'com.gradle.plugin-publish' version '0.9.4' + id 'maven-publish' + id 'java-gradle-plugin' } -apply plugin:'groovy' - -group='com.stehno' -version='0.2.4' +group = 'com.stehno' +version = '0.3.0' // explicit requests were made to build under Java 7 sourceCompatibility = 7 targetCompatibility = 7 repositories { - jcenter() + jcenter() } dependencies { - compile gradleApi() - compile localGroovy() + compile gradleApi() + compile localGroovy() - testCompile 'junit:junit:4.11' - testCompile 'org.mockito:mockito-all:1.9.5' + testCompile 'junit:junit:4.12' + testCompile('org.spockframework:spock-core:1.0-groovy-2.4') { + exclude module: 'groovy-all' + } } -task wrapper( type:Wrapper) { - gradleVersion = '2.3' +task wrapper(type: Wrapper) { + gradleVersion = '2.14' } pluginBundle { website = 'http://cjstehno.github.io/gradle-natives/' vcsUrl = 'https://github.com/cjstehno/gradle-natives' - description = 'Gradle plugin to aid in managing native libraries associated with Java-based projects.' + description = 'Gradle plugin to aid in handling native libraries associated with Java-based projects.' tags = ['gradle', 'groovy', 'native'] plugins { @@ -56,3 +44,10 @@ pluginBundle { } } } + +license { + header rootProject.file('license_header.txt') + ext.name = 'Christopher J. Stehno' + ext.email = 'chris@stehno.com' + ext.year = Calendar.instance.get(Calendar.YEAR) +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 3d0dee6..deedc7f 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6797311..ae30fad 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Apr 07 17:30:29 CDT 2015 +#Sat Sep 17 06:59:08 CDT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-all.zip diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 index 91a7e26..9aa616c --- a/gradlew +++ b/gradlew @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -161,4 +161,9 @@ function splitJvmOpts() { eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat index 8a0b282..f955316 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/license_header.txt b/license_header.txt new file mode 100644 index 0000000..9de2bda --- /dev/null +++ b/license_header.txt @@ -0,0 +1,13 @@ +Copyright (C) ${year} ${name} <${email}> + +Licensed 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. \ No newline at end of file diff --git a/src/main/groovy/com/stehno/gradle/natives/IncludeNativesTask.groovy b/src/main/groovy/com/stehno/gradle/natives/IncludeNativesTask.groovy new file mode 100644 index 0000000..bab82b9 --- /dev/null +++ b/src/main/groovy/com/stehno/gradle/natives/IncludeNativesTask.groovy @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 Christopher J. Stehno + * + * Licensed 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 com.stehno.gradle.natives + +import com.stehno.gradle.natives.ext.NativesExtension +import com.stehno.gradle.natives.ext.Platform +import groovy.transform.CompileStatic +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction + +import static com.stehno.gradle.natives.NativeLibResolver.resolveFiles + +/** + * Gradle task used to include the resolved native libraries in the project build based on the current configuration. + */ +@CompileStatic +class IncludeNativesTask extends DefaultTask { + + IncludeNativesTask() { + group = 'Natives' + description = 'Includes the resolved native libraries in the build artifacts.' + } + + @TaskAction @SuppressWarnings('GroovyUnusedDeclaration') void includeNatives() { + NativesExtension extension = project.extensions.findByType(NativesExtension) ?: new NativesExtension() + + logger.info "Including native libraries found for configurations (${extension.configurations.join(', ')})..." + + resolveFiles(project, extension).findAll { File art, List libs -> libs }.each { File art, List libs -> + logger.info " - ${art.name}:" + libs.each { lib -> + File outputDir = outputDir(lib.platform, extension.outputDir) + if (!outputDir.exists()) outputDir.mkdirs() + + logger.info "\t[${lib.platform.name()}] ${lib.entry.name} --> $outputDir" + + new File(outputDir, lib.entry.name).bytes = lib.jar.getInputStream(lib.entry).bytes + } + } + } + + private File outputDir(final Platform platform, final String dir) { + String outd = dir.replaceAll(':platform', platform.os) + new File(outd.startsWith('/') ? outd : "${project.buildDir}/$outd" as String) + } +} diff --git a/src/main/groovy/com/stehno/gradle/natives/ListNativesTask.groovy b/src/main/groovy/com/stehno/gradle/natives/ListNativesTask.groovy new file mode 100644 index 0000000..fae2e9d --- /dev/null +++ b/src/main/groovy/com/stehno/gradle/natives/ListNativesTask.groovy @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 Christopher J. Stehno + * + * Licensed 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 com.stehno.gradle.natives + +import com.stehno.gradle.natives.ext.NativesExtension +import groovy.transform.CompileStatic +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction + +import static com.stehno.gradle.natives.NativeLibResolver.resolveNames + +/** + * Gradle task used to list the native libraries resolved by the applied configuration. + */ +@CompileStatic +class ListNativesTask extends DefaultTask { + + ListNativesTask() { + group = 'Natives' + description = 'Lists all native libraries in the configured libraries.' + dependsOn 'build' + } + + @TaskAction @SuppressWarnings('GroovyUnusedDeclaration') void listNatives() { + NativesExtension extension = project.extensions.findByType(NativesExtension) ?: new NativesExtension() + + logger.lifecycle "Native libraries found for configurations (${extension.configurations.join(', ')})..." + + resolveNames(project, extension).findAll { File art, List libs -> libs }.each { File art, List libs -> + logger.lifecycle " - ${art.name}:" + libs.each { lib -> + logger.lifecycle "\t[${lib.platform.name()}] ${lib.name}" + } + } + } +} diff --git a/src/main/groovy/com/stehno/gradle/natives/NativeLibResolver.groovy b/src/main/groovy/com/stehno/gradle/natives/NativeLibResolver.groovy new file mode 100644 index 0000000..c40f439 --- /dev/null +++ b/src/main/groovy/com/stehno/gradle/natives/NativeLibResolver.groovy @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2016 Christopher J. Stehno + * + * Licensed 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 com.stehno.gradle.natives + +import com.stehno.gradle.natives.ext.LibraryFilter +import com.stehno.gradle.natives.ext.NativesExtension +import com.stehno.gradle.natives.ext.Platform +import groovy.transform.CompileStatic +import groovy.transform.Immutable +import org.gradle.api.Project +import org.gradle.api.artifacts.ResolvedArtifact +import org.gradle.api.artifacts.ResolvedDependency + +import java.util.jar.JarEntry +import java.util.jar.JarFile + +/** + * Searches the project and resolves the native libraries based on the supplied configuration. + */ +@CompileStatic +class NativeLibResolver { + // TODO: this could do with some refactoring/cleanup + + /** + * Resolves the natives matching the supplied configuration - only the names are resolved. + * + * @param project + * @param extension + * @return + */ + static Map> resolveNames(final Project project, final NativesExtension extension) { + Map> foundLibs = [:] + + findDependencyArtifacts(project, extension.configurations).each { File artifactFile -> + foundLibs[artifactFile] = [] as List + + (extension.platforms as Collection).each { Platform platform -> + Set nativeLibs = findNatives(platform, artifactFile, extension.libraries) { JarFile jar, JarEntry entry -> entry.name } + nativeLibs.each { String lib -> + (foundLibs[artifactFile] as List) << new NativeLibName(platform, lib) + } + } + } + + foundLibs + } + + /** + * Resolves the natives matching the supplied configuration. + * + * @param project + * @param extension + * @return + */ + static Map> resolveFiles(final Project project, final NativesExtension extension) { + Map> foundLibs = [:] + + findDependencyArtifacts(project, extension.configurations).each { File artifactFile -> + foundLibs[artifactFile] = [] as List + + (extension.platforms as Collection).each { Platform platform -> + Set nativeLibs = findNatives(platform, artifactFile, extension.libraries) { JarFile jar, JarEntry entry -> + new NativeLibFile(platform, jar, entry) + } + (foundLibs[artifactFile] as List).addAll(nativeLibs) + } + } + + foundLibs + } + + static Set findNatives(final Platform platform, final File artifactFile, final LibraryFilter filter, final Closure extractor) { + Set libs = new HashSet<>() + + JarFile jar = new JarFile(artifactFile) + jar.entries().findAll { JarEntry entry -> + platform.acceptsExtension(entry.name) + }.findAll { entry -> + !filter.include || (entry as JarEntry).name in filter.include + }.findAll { entry -> + !filter.exclude || !((entry as JarEntry).name in filter.exclude) + }.collect { entry -> + libs << extractor.call(jar, entry) + } + + libs + } + + static Set findDependencyArtifacts(final Project project, final Collection configurations) { + Set coords = new HashSet<>() + + (configurations ?: project.configurations.names).each { String cname -> + project.configurations.getByName(cname).resolvedConfiguration.firstLevelModuleDependencies.each { ResolvedDependency dep -> + collectDependencies(coords, dep) + } + } + + coords + } + + private static void collectDependencies(final Set found, final ResolvedDependency dep) { + dep.moduleArtifacts.each { ResolvedArtifact artifact -> + found << artifact.file + } + dep.children.each { child -> + collectDependencies(found, child) + } + } +} + +@Immutable +class NativeLibName { + + Platform platform + String name +} + +@Immutable(knownImmutableClasses = [JarFile, JarEntry]) +class NativeLibFile { + + Platform platform + JarFile jar + JarEntry entry +} \ No newline at end of file diff --git a/src/main/groovy/com/stehno/gradle/natives/NativesPlugin.groovy b/src/main/groovy/com/stehno/gradle/natives/NativesPlugin.groovy index 3b1eed2..b041736 100644 --- a/src/main/groovy/com/stehno/gradle/natives/NativesPlugin.groovy +++ b/src/main/groovy/com/stehno/gradle/natives/NativesPlugin.groovy @@ -1,11 +1,11 @@ /* - * Copyright (c) 2014 Christopher J. Stehno + * Copyright (C) 2016 Christopher J. Stehno * * Licensed 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 + * 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, @@ -13,51 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.stehno.gradle.natives +import com.stehno.gradle.natives.ext.NativesExtension import org.gradle.api.Plugin import org.gradle.api.Project -import java.util.jar.JarFile - /** * Gradle plugin providing assistance with managing the native components of Java-based libraries. */ class NativesPlugin implements Plugin { - private static final String EXTENSION_NAME = 'natives' - - void apply( final Project project ){ - NativesPluginExtension extension = project.extensions.create(EXTENSION_NAME, NativesPluginExtension) + void apply(final Project project) { + project.extensions.create('natives', NativesExtension) - // FIXME: do I need this? - project.task 'unpackNatives', type:UnpackNativesTask - - configureUnpackNativesTask project, extension - } - - private void configureUnpackNativesTask( final Project project, NativesPluginExtension extension ){ - project.tasks.withType(UnpackNativesTask){ - conventionMapping.map('platformJars'){ - def inputJars = new PlatformJars() - - def configuredJars = extension.configuredJars() - if( configuredJars ){ - extension.configuredPlatforms().each { platform-> - File platformDir = project.file("${project.buildDir}/natives/${platform.os}") - - project.mkdir platformDir - - project.files( project.configurations.compile ).findAll { jf-> jf.name in configuredJars }.each { njf -> - inputJars[platform] = new JarFile(njf) - } - } - } - - return inputJars - } - } + project.task 'listNatives', type: ListNativesTask + project.task 'includeNatives', type: IncludeNativesTask } } - diff --git a/src/main/groovy/com/stehno/gradle/natives/NativesPluginExtension.groovy b/src/main/groovy/com/stehno/gradle/natives/NativesPluginExtension.groovy deleted file mode 100644 index 06179b5..0000000 --- a/src/main/groovy/com/stehno/gradle/natives/NativesPluginExtension.groovy +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2014 Christopher J. Stehno - * - * Licensed 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 com.stehno.gradle.natives - -/** - * Configuration extension for the Natives plugin. - */ -class NativesPluginExtension { - - // FIXME: allow custom platforms as string format 'os:extension' - - /** - * Defines the collection of jar name(s) (on the classpath) to be searched for native libraries. - * Jar extension (.jar) will be added if omitted. - */ - def jars - - /** - * Used to specify the set of platform natives to be processed by the plugin. By default, if this - * property is not specified, all supported library types are processed. - * - * Accepts a single or collection of Platform enum values, or a single or collection of Platform OS - * strings (as from Platform.os property). Strings and Platform objects may be mixed in the - * property definition. - */ - def platforms - - /** - * Retrieves the collection of configured jars normalized for use by the plugin. - * - * @return a collection of jar name strings - */ - final Set configuredJars(){ - def libs = [] as Set - - if( jars && jars instanceof Collection ){ - libs = jars.collect( normalizeName ) - - } else if( jars ){ - libs << normalizeName(jars as String) - } - - return libs - } - - /** - * Retrieves the collection of configured platform values for use by the plugin. - * - * @return a collection of Platform enum values. - */ - final Set configuredPlatforms(){ - def plats = [] as Set - - if( !platforms ){ - plats.addAll( Platform.values() ) - - } else if( platforms instanceof Collection ){ - platforms.each { p-> - def plat = asPlatform( p ) - if( plat ) plats << plat - } - - } else { - def plat = asPlatform( platforms ) - if( plat ) plats << plat - } - - return plats - } - - private Platform asPlatform( final plat ){ - plat instanceof Platform ? plat : Platform.fromOs( plat as String ) - } - - private normalizeName = { j-> - (j.endsWith('.jar') ? j : "${j}.jar") as String - } -} \ No newline at end of file diff --git a/src/main/groovy/com/stehno/gradle/natives/PlatformJars.groovy b/src/main/groovy/com/stehno/gradle/natives/PlatformJars.groovy deleted file mode 100644 index 35f55c6..0000000 --- a/src/main/groovy/com/stehno/gradle/natives/PlatformJars.groovy +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2014 Christopher J. Stehno - * - * Licensed 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 com.stehno.gradle.natives - -import java.util.jar.JarFile - -/** - * Simple container for jar references by platform. - */ -class PlatformJars { - - private final Map> jars = new EnumMap<>(Platform) - - void putAt(final Platform platform, final JarFile jar) { - def collection = jars[platform] - if (!collection) { - jars[platform] = [jar] - } else { - collection << jar - } - } - - void each(Closure closure) { - jars.each { k,v-> - v.each { i-> - closure( k, i ) - } - } - } -} diff --git a/src/main/groovy/com/stehno/gradle/natives/UnpackNativesTask.groovy b/src/main/groovy/com/stehno/gradle/natives/UnpackNativesTask.groovy deleted file mode 100644 index 6972028..0000000 --- a/src/main/groovy/com/stehno/gradle/natives/UnpackNativesTask.groovy +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2014 Christopher J. Stehno - * - * Licensed 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 com.stehno.gradle.natives - -import org.gradle.api.DefaultTask -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.TaskAction - -import java.util.jar.JarEntry -import java.util.jar.JarFile - -/** - * Task used to unpack the native library files from project dependency jar files. - */ -class UnpackNativesTask extends DefaultTask { - - @Input - PlatformJars platformJars - - UnpackNativesTask(){ - name = 'unpackNatives' - group = 'Natives' - description = 'Unpacks native library files from project dependency jar files.' - dependsOn 'build' - - println project.natives - } - - @TaskAction void unpackNatives(){ - // specific getter required - odd Input-related requirement - getPlatformJars()?.each { Platform platform, JarFile jarFile-> - jarFile.entries().findAll { JarEntry je-> platform.acceptsExtension( je.name ) }.each { JarEntry jef-> - logger.info 'Unpacking: {}', jef.name - - project.file("${project.buildDir}/natives/${platform.os}/${jef.name}").bytes = jarFile.getInputStream(jef).bytes - } - } - } -} \ No newline at end of file diff --git a/src/main/groovy/com/stehno/gradle/natives/ext/LibraryFilter.groovy b/src/main/groovy/com/stehno/gradle/natives/ext/LibraryFilter.groovy new file mode 100644 index 0000000..ae99112 --- /dev/null +++ b/src/main/groovy/com/stehno/gradle/natives/ext/LibraryFilter.groovy @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 Christopher J. Stehno + * + * Licensed 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 com.stehno.gradle.natives.ext + +import groovy.transform.TypeChecked + +/** + * Filter object used to limit the scope of the resolved libraries. + */ +@TypeChecked +class LibraryFilter { + + /** + * Limits the resolved libraries to only those provided in the list. + */ + Collection include = [] + + /** + * Limits the resolved libraries to only those NOT provided in the list. + */ + Collection exclude = [] +} diff --git a/src/main/groovy/com/stehno/gradle/natives/ext/NativesExtension.groovy b/src/main/groovy/com/stehno/gradle/natives/ext/NativesExtension.groovy new file mode 100644 index 0000000..486fbd4 --- /dev/null +++ b/src/main/groovy/com/stehno/gradle/natives/ext/NativesExtension.groovy @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 Christopher J. Stehno + * + * Licensed 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 com.stehno.gradle.natives.ext + +import groovy.transform.TypeChecked + +/** + * DSL Extension for configuring the native library resolution. + */ +@TypeChecked +class NativesExtension { + + private LibraryFilter libraries = new LibraryFilter() + + /** + * Specifies the build configurations which will be scanned for native libraries. The default is to search the "compile" and "runtime" + * configurations. + */ + Collection configurations = ['compile', 'runtime'] + + /** + * Used to specify the OS platforms whose libraries will be resolved. All platforms are retrieved by default. + */ + Collection platforms = Platform.all() + + /** + * Used to specify the native library output directory. Defaults to natives. + * + * A relative path will be based on the project build directory. + * + * A replacement token :platform may be added which will be replaced by the platform value for the each native library. + */ + String outputDir = 'natives' + + /** + * Used to apply a filter to the search. No filtering by default. + */ + void libraries(@DelegatesTo(LibraryFilter) Closure closure) { + def filter = new LibraryFilter() + closure.delegate = filter + closure.call() + libraries = filter + } + + LibraryFilter getLibraries() { + libraries + } +} diff --git a/src/main/groovy/com/stehno/gradle/natives/Platform.groovy b/src/main/groovy/com/stehno/gradle/natives/ext/Platform.groovy similarity index 59% rename from src/main/groovy/com/stehno/gradle/natives/Platform.groovy rename to src/main/groovy/com/stehno/gradle/natives/ext/Platform.groovy index 5a06406..280b112 100644 --- a/src/main/groovy/com/stehno/gradle/natives/Platform.groovy +++ b/src/main/groovy/com/stehno/gradle/natives/ext/Platform.groovy @@ -1,11 +1,11 @@ /* - * Copyright (c) 2014 Christopher J. Stehno + * Copyright (C) 2016 Christopher J. Stehno * * Licensed 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 + * 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, @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package com.stehno.gradle.natives +package com.stehno.gradle.natives.ext import groovy.transform.ToString @@ -23,27 +22,27 @@ import groovy.transform.ToString */ @ToString enum Platform { - WINDOWS( 'windows', ['.dll'] ), - LINUX( 'linux', ['.so'] ), - MAC( 'osx', ['.jnilib', '.dylib'] ) + WINDOWS('windows', ['.dll']), + LINUX('linux', ['.so']), + MAC('osx', ['.jnilib', '.dylib']) final String os final Set extensions = [] as Set - private Platform( final String os, final Collection extensions ){ + private Platform(final String os, final Collection extensions) { this.os = os - this.extensions.addAll( extensions ) + this.extensions.addAll(extensions) } /** * Used to determine whether or not the provided file name has an extension acceptable to the platform. * * @param name the file name - * @return true, if the file extension is accepted by the platform + * @return true , if the file extension is accepted by the platform */ - boolean acceptsExtension( final String name ){ + boolean acceptsExtension(final String name) { extensions.any { - name.toLowerCase().endsWith( it ) + name.toLowerCase().endsWith(it) } } @@ -54,26 +53,35 @@ enum Platform { * @param os the os name to be converted to a Plaform * @return the Platform value or null if not found. */ - static Platform fromOs( final String os ){ + static Platform fromOs(final String os) { values().find { it.os == os } } + /** + * Alias to values(). Retrieves a collection of all the platform values. + * + * @return a Collection of all the platform values. + */ + static Collection all() { + values() + } + /** * Used to determine the current platform being run by the build. * * @return the platform for the current OS or null if not directly supported. */ - static Platform current(){ + static Platform current() { Platform currPlat = null - String osName = System.getProperty( 'os.name' ).toLowerCase() - if( osName.startsWith( Platform.WINDOWS.os ) ){ + String osName = System.getProperty('os.name').toLowerCase() + if (osName.startsWith(Platform.WINDOWS.os)) { currPlat = Platform.WINDOWS - } else if( osName.contains( Platform.MAC.os ) || osName.contains( 'mac' ) ){ + } else if (osName.contains(Platform.MAC.os) || osName.contains('mac')) { currPlat = Platform.MAC - } else if( osName.contains( Platform.LINUX.os ) ){ + } else if (osName.contains(Platform.LINUX.os)) { currPlat = Platform.LINUX } diff --git a/src/main/resources/META-INF/gradle-plugins/com.stehno.natives.properties b/src/main/resources/META-INF/gradle-plugins/com.stehno.natives.properties index c37d8fd..9a02c7a 100644 --- a/src/main/resources/META-INF/gradle-plugins/com.stehno.natives.properties +++ b/src/main/resources/META-INF/gradle-plugins/com.stehno.natives.properties @@ -1 +1,17 @@ +# +# Copyright (C) 2016 Christopher J. Stehno +# +# Licensed 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. +# + implementation-class=com.stehno.gradle.natives.NativesPlugin \ No newline at end of file diff --git a/src/test/groovy/com/stehno/gradle/natives/IncludeNativesTaskSpec.groovy b/src/test/groovy/com/stehno/gradle/natives/IncludeNativesTaskSpec.groovy new file mode 100644 index 0000000..fe9b4d9 --- /dev/null +++ b/src/test/groovy/com/stehno/gradle/natives/IncludeNativesTaskSpec.groovy @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2016 Christopher J. Stehno + * + * Licensed 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 com.stehno.gradle.natives + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.BuildTask +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import spock.lang.Specification + +class IncludeNativesTaskSpec extends Specification { + + @Rule public TemporaryFolder projectDir = new TemporaryFolder() + + def 'includeNatives with no dependencies should succeed'() { + given: + buildFile() + + when: + BuildResult result = gradleRunner(['includeNatives']).build() + + then: + totalSuccess result + } + + def 'includeNatives (all platforms)'() { + given: + buildFile([ + dependencies: ''' + compile 'org.lwjgl:lwjgl:3.0.0' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-windows' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-linux' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-osx' + '''.stripIndent(), + natives : ''' + natives { + platforms = Platform.all() + } + '''.stripIndent() + ]) + + when: + BuildResult result = gradleRunner(['includeNatives']).build() + + then: + totalSuccess(result) + nativeFilesExist projectDir.root, [ + 'build/natives/lwjgl.dll', + 'build/natives/lwjgl32.dll', + 'build/natives/OpenAL.dll', + 'build/natives/jemalloc.dll', + 'build/natives/glfw.dll', + 'build/natives/glfw32.dll', + 'build/natives/jemalloc32.dll', + 'build/natives/OpenAL32.dll', + 'build/natives/liblwjgl.dylib', + 'build/natives/libjemalloc.dylib', + 'build/natives/libglfw.dylib', + 'build/natives/libopenal.dylib', + 'build/natives/libjemalloc.so', + 'build/natives/liblwjgl.so', + 'build/natives/libglfw.so', + 'build/natives/libopenal.so' + ] + } + + def 'includeNatives (all by platforms)'() { + given: + buildFile([ + dependencies: ''' + compile 'org.lwjgl:lwjgl:3.0.0' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-windows' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-linux' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-osx' + '''.stripIndent(), + natives : ''' + natives { + platforms = Platform.all() + outputDir = 'natives/:platform' + } + '''.stripIndent() + ]) + + when: + BuildResult result = gradleRunner(['includeNatives']).build() + + then: + totalSuccess(result) + nativeFilesExist projectDir.root, [ + 'build/natives/windows/lwjgl.dll', + 'build/natives/windows/lwjgl32.dll', + 'build/natives/windows/OpenAL.dll', + 'build/natives/windows/jemalloc.dll', + 'build/natives/windows/glfw.dll', + 'build/natives/windows/glfw32.dll', + 'build/natives/windows/jemalloc32.dll', + 'build/natives/windows/OpenAL32.dll', + 'build/natives/osx/liblwjgl.dylib', + 'build/natives/osx/libjemalloc.dylib', + 'build/natives/osx/libglfw.dylib', + 'build/natives/osx/libopenal.dylib', + 'build/natives/linux/libjemalloc.so', + 'build/natives/linux/liblwjgl.so', + 'build/natives/linux/libglfw.so', + 'build/natives/linux/libopenal.so' + ] + } + + def 'includeNatives (all by platforms, exclude)'() { + given: + buildFile([ + dependencies: ''' + compile 'org.lwjgl:lwjgl:3.0.0' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-windows' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-linux' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-osx' + '''.stripIndent(), + natives : ''' + natives { + platforms = Platform.all() + outputDir = 'natives/:platform' + libraries { + exclude = ['lwjgl.dll', 'liblwjgl.dylib'] + } + } + '''.stripIndent() + ]) + + when: + BuildResult result = gradleRunner(['includeNatives']).build() + + then: + totalSuccess(result) + nativeFilesExist projectDir.root, [ + 'build/natives/windows/lwjgl32.dll', + 'build/natives/windows/OpenAL.dll', + 'build/natives/windows/jemalloc.dll', + 'build/natives/windows/glfw.dll', + 'build/natives/windows/glfw32.dll', + 'build/natives/windows/jemalloc32.dll', + 'build/natives/windows/OpenAL32.dll', + 'build/natives/osx/libjemalloc.dylib', + 'build/natives/osx/libglfw.dylib', + 'build/natives/osx/libopenal.dylib', + 'build/natives/linux/libjemalloc.so', + 'build/natives/linux/liblwjgl.so', + 'build/natives/linux/libglfw.so', + 'build/natives/linux/libopenal.so' + ] + nativeFilesDontExist projectDir.root, [ + 'build/natives/windows/lwjgl.dll', + 'build/natives/osx/liblwjgl.dylib' + ] + } + + private static boolean nativeFilesExist(final File root, Collection paths) { + paths.every { path -> new File(root, path).exists() } + } + + private static boolean nativeFilesDontExist(final File root, Collection paths) { + paths.every { path -> !new File(root, path).exists() } + } + + // TODO: make all these helper methods shared (might be useful as a vanilla-gradle library) + + private void buildFile(final Map config = [:]) { + File buildFile = projectDir.newFile('build.gradle') + buildFile.text = """ + import com.stehno.gradle.natives.ext.Platform + + plugins { + id 'com.stehno.natives' + id 'java' + } + repositories { + jcenter() + } + dependencies { + ${config.dependencies ?: ''} + } + ${config.natives ?: ''} + """.stripIndent() + } + + private GradleRunner gradleRunner(final List args) { + GradleRunner.create().withPluginClasspath().withDebug(true).withProjectDir(projectDir.root).withArguments(args) + } + + private static boolean totalSuccess(final BuildResult result) { + result.tasks.every { BuildTask task -> + task.outcome == TaskOutcome.SUCCESS + } + } + + private static boolean textContainsLines(final String text, final Collection lines, final boolean trimmed = true) { + lines.every { String line -> + text.contains(trimmed ? line.trim() : line) + } + } + + private static boolean textDoesNotContainLines(final String text, final Collection lines, final boolean trimmed = true) { + lines.every { String line -> + !text.contains(trimmed ? line.trim() : line) + } + } +} diff --git a/src/test/groovy/com/stehno/gradle/natives/ListNativesTaskSpec.groovy b/src/test/groovy/com/stehno/gradle/natives/ListNativesTaskSpec.groovy new file mode 100644 index 0000000..60c1c7a --- /dev/null +++ b/src/test/groovy/com/stehno/gradle/natives/ListNativesTaskSpec.groovy @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2016 Christopher J. Stehno + * + * Licensed 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 com.stehno.gradle.natives + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.BuildTask +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import spock.lang.Specification + +class ListNativesTaskSpec extends Specification { + + // TODO: test with different gradle versions: .withGradleVersion() + + @Rule public TemporaryFolder projectDir = new TemporaryFolder() + + def 'listNatives with no dependencies should succeed'() { + given: 'an build file with no native configuration and no native dependencies' + buildFile() + + when: 'the task is run' + BuildResult result = gradleRunner(['listNatives']).build() + + then: 'the build should pass' + totalSuccess result + } + + def 'listNatives with native dependencies should list them (default config)'() { + given: + buildFile([ + dependencies: /compile 'org.lwjgl.lwjgl:lwjgl:2.9.1'/ + ]) + + when: 'the task is run' + BuildResult result = gradleRunner(['listNatives']).build() + + then: + textContainsLines result.output, [ + '- jinput-platform-2.0.5-natives-linux.jar:', + '[LINUX] libjinput-linux.so', + '[LINUX] libjinput-linux64.so', + '- lwjgl-platform-2.9.1-natives-linux.jar:', + '[LINUX] libopenal64.so', + '[LINUX] liblwjgl.so', + '[LINUX] liblwjgl64.so', + '[LINUX] libopenal.so' + ] + } + + def 'listNatives with native dependencies should list them (windows)'() { + given: + buildFile([ + dependencies: /compile 'org.lwjgl.lwjgl:lwjgl:2.9.1'/, + natives : ''' + natives { + platforms = [Platform.WINDOWS] + } + '''.stripIndent() + ]) + + when: 'the task is run' + BuildResult result = gradleRunner(['listNatives']).build() + + then: + textContainsLines result.output, [ + '- lwjgl-platform-2.9.1-natives-windows.jar:', + '[WINDOWS] lwjgl.dll', + '[WINDOWS] OpenAL64.dll', + '[WINDOWS] OpenAL32.dll', + '[WINDOWS] lwjgl64.dll', + '- jinput-platform-2.0.5-natives-windows.jar:', + '[WINDOWS] jinput-dx8_64.dll', + '[WINDOWS] jinput-dx8.dll', + '[WINDOWS] jinput-wintab.dll', + '[WINDOWS] jinput-raw_64.dll', + '[WINDOWS] jinput-raw.dll' + ] + } + + def 'listNatives with native dependencies should list them (windows,linux)'() { + given: + buildFile([ + dependencies: /compile 'org.lwjgl.lwjgl:lwjgl:2.9.1'/, + natives : ''' + natives { + platforms = [Platform.WINDOWS, Platform.LINUX] + } + '''.stripIndent() + ]) + + when: 'the task is run' + BuildResult result = gradleRunner(['listNatives']).build() + + then: + textContainsLines result.output, [ + '- lwjgl-platform-2.9.1-natives-windows.jar:', + '[WINDOWS] lwjgl.dll', + '[WINDOWS] OpenAL64.dll', + '[WINDOWS] OpenAL32.dll', + '[WINDOWS] lwjgl64.dll', + '- jinput-platform-2.0.5-natives-linux.jar:', + '[LINUX] libjinput-linux.so', + '[LINUX] libjinput-linux64.so', + '- lwjgl-platform-2.9.1-natives-linux.jar:', + '[LINUX] libopenal64.so', + '[LINUX] liblwjgl.so', + '[LINUX] liblwjgl64.so', + '[LINUX] libopenal.so', + '- jinput-platform-2.0.5-natives-windows.jar:', + '[WINDOWS] jinput-dx8_64.dll', + '[WINDOWS] jinput-dx8.dll', + '[WINDOWS] jinput-wintab.dll', + '[WINDOWS] jinput-raw_64.dll', + '[WINDOWS] jinput-raw.dll' + ] + } + + def 'listNatives for some of the trouble libs on all platforms'() { + given: + buildFile([ + dependencies: ''' + compile 'org.lwjgl:lwjgl:3.0.0' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-windows' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-linux' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-osx' + '''.stripIndent(), + natives : ''' + natives { + platforms = Platform.all() + } + '''.stripIndent() + ]) + + when: 'the task is run' + BuildResult result = gradleRunner(['listNatives']).build() + + then: + textContainsLines result.output, [ + '- lwjgl-platform-3.0.0-natives-windows.jar:', + '[WINDOWS] lwjgl.dll', + '[WINDOWS] lwjgl32.dll', + '[WINDOWS] OpenAL.dll', + '[WINDOWS] jemalloc.dll', + '[WINDOWS] glfw.dll', + '[WINDOWS] glfw32.dll', + '[WINDOWS] jemalloc32.dll', + '[WINDOWS] OpenAL32.dll', + '- lwjgl-platform-3.0.0-natives-osx.jar:', + '[MAC] liblwjgl.dylib', + '[MAC] libjemalloc.dylib', + '[MAC] libglfw.dylib', + '[MAC] libopenal.dylib', + '- lwjgl-platform-3.0.0-natives-linux.jar:', + '[LINUX] libjemalloc.so', + '[LINUX] liblwjgl.so', + '[LINUX] libglfw.so', + '[LINUX] libopenal.so' + ] + } + + def 'listNatives for some of the trouble libs on all platforms (lib excludes)'() { + given: + buildFile([ + dependencies: ''' + compile 'org.lwjgl:lwjgl:3.0.0' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-windows' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-linux' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-osx' + '''.stripIndent(), + natives : ''' + natives { + platforms = Platform.all() + libraries { + exclude = ['lwjgl32.dll', 'libjemalloc.dylib'] + } + } + '''.stripIndent() + ]) + + when: 'the task is run' + BuildResult result = gradleRunner(['listNatives']).build() + + then: + textContainsLines result.output, [ + '- lwjgl-platform-3.0.0-natives-windows.jar:', + '[WINDOWS] lwjgl.dll', + '[WINDOWS] OpenAL.dll', + '[WINDOWS] jemalloc.dll', + '[WINDOWS] glfw.dll', + '[WINDOWS] glfw32.dll', + '[WINDOWS] jemalloc32.dll', + '[WINDOWS] OpenAL32.dll', + '- lwjgl-platform-3.0.0-natives-osx.jar:', + '[MAC] liblwjgl.dylib', + '[MAC] libglfw.dylib', + '[MAC] libopenal.dylib', + '- lwjgl-platform-3.0.0-natives-linux.jar:', + '[LINUX] libjemalloc.so', + '[LINUX] liblwjgl.so', + '[LINUX] libglfw.so', + '[LINUX] libopenal.so' + ] + textDoesNotContainLines result.output, ['[WINDOWS] lwjgl32.dll', '[MAC] libjemalloc.dylib',] + } + + def 'listNatives for some of the trouble libs on all platforms (includes filter)'() { + given: + buildFile([ + dependencies: ''' + compile 'org.lwjgl:lwjgl:3.0.0' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-windows' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-linux' + compile 'org.lwjgl:lwjgl-platform:3.0.0:natives-osx' + '''.stripIndent(), + natives : ''' + natives { + platforms = Platform.all() + libraries { + include = ['OpenAL.dll', 'libopenal.dylib', 'libopenal.so'] + } + } + '''.stripIndent() + ]) + + when: 'the task is run' + BuildResult result = gradleRunner(['listNatives']).build() + + then: + textContainsLines result.output, [ + '- lwjgl-platform-3.0.0-natives-windows.jar:', + '[WINDOWS] OpenAL.dll', + '- lwjgl-platform-3.0.0-natives-osx.jar:', + '[MAC] libopenal.dylib', + '- lwjgl-platform-3.0.0-natives-linux.jar:', + '[LINUX] libopenal.so' + ] + textDoesNotContainLines result.output, [ + '[WINDOWS] lwjgl.dll', + '[WINDOWS] lwjgl32.dll', + '[WINDOWS] jemalloc.dll', + '[WINDOWS] glfw.dll', + '[WINDOWS] glfw32.dll', + '[WINDOWS] jemalloc32.dll', + '[WINDOWS] OpenAL32.dll', + '[MAC] liblwjgl.dylib', + '[MAC] libjemalloc.dylib', + '[MAC] libglfw.dylib', + '[LINUX] libjemalloc.so', + '[LINUX] liblwjgl.so', + '[LINUX] libglfw.so', + ] + } + + private void buildFile(final Map config = [:]) { + File buildFile = projectDir.newFile('build.gradle') + buildFile.text = """ + import com.stehno.gradle.natives.ext.Platform + + plugins { + id 'com.stehno.natives' + id 'java' + } + repositories { + jcenter() + } + dependencies { + ${config.dependencies ?: ''} + } + ${config.natives ?: ''} + """.stripIndent() + } + + private GradleRunner gradleRunner(final List args) { + GradleRunner.create().withPluginClasspath().withDebug(true).withProjectDir(projectDir.root).withArguments(args) + } + + private static boolean totalSuccess(final BuildResult result) { + result.tasks.every { BuildTask task -> + task.outcome == TaskOutcome.SUCCESS || task.outcome == TaskOutcome.UP_TO_DATE + } + } + + private static boolean textContainsLines(final String text, final Collection lines, final boolean trimmed = true) { + lines.every { String line -> + text.contains(trimmed ? line.trim() : line) + } + } + + private static boolean textDoesNotContainLines(final String text, final Collection lines, final boolean trimmed = true) { + lines.every { String line -> + !text.contains(trimmed ? line.trim() : line) + } + } +} diff --git a/src/test/groovy/com/stehno/gradle/natives/NativeLibResolverSpec.groovy b/src/test/groovy/com/stehno/gradle/natives/NativeLibResolverSpec.groovy new file mode 100644 index 0000000..79cd2e5 --- /dev/null +++ b/src/test/groovy/com/stehno/gradle/natives/NativeLibResolverSpec.groovy @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 Christopher J. Stehno + * + * Licensed 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 com.stehno.gradle.natives + +import com.stehno.gradle.natives.ext.LibraryFilter +import spock.lang.Specification +import spock.lang.Unroll + +import static com.stehno.gradle.natives.NativeLibResolver.findNatives +import static com.stehno.gradle.natives.ext.Platform.* + +class NativeLibResolverSpec extends Specification { + + @Unroll def 'findNatives: #platform (no filters)'() { + given: + File file = new File(NativeLibResolverSpec.getResource("/lwjgl-platform-2.9.1-natives-${platform.os}.jar").toURI()) + + when: + def natives = findNatives(platform, file, new LibraryFilter()) { jar, entry -> entry.name } + + then: + natives.size() == results.size() + natives.containsAll(results) + + where: + platform || results + WINDOWS || ['lwjgl.dll', 'OpenAL64.dll', 'OpenAL32.dll', 'lwjgl64.dll'] + LINUX || ['libopenal64.so', 'liblwjgl.so', 'liblwjgl64.so', 'libopenal.so'] + MAC || ['openal.dylib', 'liblwjgl.jnilib'] + } + + @Unroll def 'findNatives: #platform (includes)'() { + given: + File file = new File(NativeLibResolverSpec.getResource("/lwjgl-platform-2.9.1-natives-${platform.os}.jar").toURI()) + + when: + def natives = findNatives(platform, file, new LibraryFilter( + include: ['libopenal64.so', 'openal.dylib', 'OpenAL64.dll'] + )) { jar, entry -> entry.name } + + then: + natives.size() == results.size() + natives.containsAll(results) + + where: + platform || results + WINDOWS || ['OpenAL64.dll'] + LINUX || ['libopenal64.so'] + MAC || ['openal.dylib'] + } + + @Unroll def 'findNatives: #platform (excludes)'() { + given: + File file = new File(NativeLibResolverSpec.getResource("/lwjgl-platform-2.9.1-natives-${platform.os}.jar").toURI()) + + when: + def natives = findNatives(platform, file, new LibraryFilter( + exclude: ['lwjgl.dll', 'libopenal64.so', 'openal.dylib'] + )) { jar, entry -> entry.name } + + then: + natives.size() == results.size() + natives.containsAll(results) + + where: + platform || results + WINDOWS || ['OpenAL64.dll', 'OpenAL32.dll', 'lwjgl64.dll'] + LINUX || ['liblwjgl.so', 'liblwjgl64.so', 'libopenal.so'] + MAC || ['liblwjgl.jnilib'] + } +} diff --git a/src/test/groovy/com/stehno/gradle/natives/NativesPluginExtensionTest.groovy b/src/test/groovy/com/stehno/gradle/natives/NativesPluginExtensionTest.groovy deleted file mode 100644 index 7f540d5..0000000 --- a/src/test/groovy/com/stehno/gradle/natives/NativesPluginExtensionTest.groovy +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2014 Christopher J. Stehno - * - * Licensed 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 com.stehno.gradle.natives - -import org.junit.Before -import org.junit.Test - -class NativesPluginExtensionTest { - - private NativesPluginExtension pluginExtension - - @Before void before(){ - pluginExtension = new NativesPluginExtension() - } - - @Test void 'empty'(){ - assert pluginExtension.configuredJars().empty - assert pluginExtension.configuredPlatforms().containsAll( Platform.values() ) - } - - @Test void 'jars: single-string (no jar)'(){ - pluginExtension.jars = 'some-library' - assertJars( 'some-library.jar' ) - } - - @Test void 'jars: single-string (with jar)'(){ - pluginExtension.jars = 'some-library.jar' - assertJars( 'some-library.jar' ) - } - - @Test void 'jars: single-collection (no jar)'(){ - pluginExtension.jars = ['some-library'] - assertJars( 'some-library.jar' ) - } - - @Test void 'jars: single-collection (with jar)'(){ - pluginExtension.jars = ['some-library.jar'] - assertJars( 'some-library.jar' ) - } - - @Test void 'platforms: single-string'(){ - pluginExtension.platforms = 'windows' - assertPlatforms( Platform.WINDOWS ) - } - - @Test void 'platforms: single-platform'(){ - pluginExtension.platforms = Platform.WINDOWS - assertPlatforms( Platform.WINDOWS ) - } - - @Test void 'platforms: collection-string'(){ - pluginExtension.platforms = ['windows','osx'] - assertPlatforms( Platform.WINDOWS, Platform.MAC ) - } - - @Test void 'platforms: collection-platform'(){ - pluginExtension.platforms = [Platform.WINDOWS, Platform.MAC] - assertPlatforms( Platform.WINDOWS, Platform.MAC ) - } - - @Test void 'platforms: collection-mixed'(){ - pluginExtension.platforms = ['windows', Platform.MAC, 'linux'] - assertPlatforms( Platform.WINDOWS, Platform.MAC, Platform.LINUX ) - } - - private void assertPlatforms( Platform... platforms ){ - def plats = pluginExtension.configuredPlatforms() - assert plats.size() == platforms.size() - assert plats.containsAll( platforms ) - } - - private void assertJars( String... jarNames ){ - def jars = pluginExtension.configuredJars() - assert jars.size() == jarNames.size() - assert jars.containsAll( jarNames ) - } -} diff --git a/src/test/groovy/com/stehno/gradle/natives/PlatformJarsTest.groovy b/src/test/groovy/com/stehno/gradle/natives/PlatformJarsTest.groovy deleted file mode 100644 index 0a85784..0000000 --- a/src/test/groovy/com/stehno/gradle/natives/PlatformJarsTest.groovy +++ /dev/null @@ -1,41 +0,0 @@ -package com.stehno.gradle.natives - -import org.junit.Test - -import java.util.jar.JarFile - -import static com.stehno.gradle.natives.Platform.LINUX -import static com.stehno.gradle.natives.Platform.WINDOWS -import static org.mockito.Mockito.mock - -class PlatformJarsTest { - - @Test void 'put & each'(){ - def platformJars = new PlatformJars() - - def winJars = [jarFile('a.jar'), jarFile('b.jar')] - def linJars = [jarFile('c.jar')] - - platformJars[WINDOWS] = winJars[0] - platformJars[WINDOWS] = winJars[1] - platformJars[LINUX] = linJars[0] - - assert platformJars.jars.size() == 2 - assert platformJars.jars[WINDOWS].size() == 2 - assert platformJars.jars[LINUX].size() == 1 - - platformJars.each { Platform p, JarFile j -> - if( p == WINDOWS ){ - assert j in winJars - } else if( p == LINUX ){ - assert j in linJars - } else { - assert false - } - } - } - - private JarFile jarFile( final String name ){ - mock(JarFile, name) - } -} diff --git a/src/test/groovy/com/stehno/gradle/natives/UnpackNativesTaskTest.groovy b/src/test/groovy/com/stehno/gradle/natives/UnpackNativesTaskTest.groovy deleted file mode 100644 index cd8dd4a..0000000 --- a/src/test/groovy/com/stehno/gradle/natives/UnpackNativesTaskTest.groovy +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2014 Christopher J. Stehno - * - * Licensed 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 com.stehno.gradle.natives -import org.gradle.api.Project -import org.gradle.testfixtures.ProjectBuilder -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder - -class UnpackNativesTaskTest { - - @Rule public TemporaryFolder projectDir = new TemporaryFolder() - - private Project project - private File projectRoot - - @Before void before(){ - projectRoot = projectDir.newFolder() - project = ProjectBuilder.builder().withProjectDir( projectRoot ).build() - - project.apply plugin:'java' - project.apply plugin:NativesPlugin - - project.repositories { - jcenter() - } - - project.dependencies { - compile 'org.lwjgl.lwjgl:lwjgl:2.9.1' - } - } - - @Test void 'identity'(){ - def task = project.tasks.unpackNatives - - assert task instanceof UnpackNativesTask - assert task.name == 'unpackNatives' - assert task.group == 'Natives' - assert task.description == 'Unpacks native library files from project dependency jar files.' - } - - @Test void 'usage: without config'(){ - def task = project.tasks.unpackNatives - - task.execute() - - assertNotExists 'build/natives/' - } - - @Test void 'usage: single platform'(){ - project.natives { - jars = ['lwjgl-platform-2.9.1-natives-windows'] - platforms = Platform.WINDOWS - } - - def task = project.tasks.unpackNatives - task.execute() - - assertUnpacked( 'windows', ['OpenAL32.dll', 'OpenAL64.dll', 'lwjgl.dll', 'lwjgl64.dll'] ) - - assertNotExists 'build/natives/linux' - assertNotExists 'build/natives/osx' - } - - @Test void 'usage: single platform non-standard build dir'(){ - project.buildDir = 'results' - project.natives { - jars = ['lwjgl-platform-2.9.1-natives-windows'] - platforms = Platform.WINDOWS - } - - def task = project.tasks.unpackNatives - task.execute() - - assertUnpacked( 'windows', ['OpenAL32.dll', 'OpenAL64.dll', 'lwjgl.dll', 'lwjgl64.dll'], 'results' ) - - assertNotExists 'results/natives/linux' - assertNotExists 'results/natives/osx' - } - - @Test void 'usage: undefined platform (all)'(){ - project.natives { - jars = [ - 'lwjgl-platform-2.9.1-natives-windows', - 'lwjgl-platform-2.9.1-natives-osx', - 'lwjgl-platform-2.9.1-natives-linux' - ] - } - - def task = project.tasks.unpackNatives - task.execute() - - assertUnpacked( 'windows', ['OpenAL32.dll', 'OpenAL64.dll', 'lwjgl.dll', 'lwjgl64.dll'] ) - assertUnpacked( 'linux', ['liblwjgl.so', 'liblwjgl64.so', 'libopenal.so', 'libopenal64.so'] ) - assertUnpacked( 'osx', ['liblwjgl.jnilib','openal.dylib'] ) - } - - private void assertNotExists( final String relPath ){ - assert !new File( projectRoot, relPath ).exists() - } - - private void assertUnpacked( final String platform, final Collection libs, final String buildDir='build'){ - assert new File( projectRoot , buildDir ).exists() - assert new File( projectRoot , "${buildDir}/natives").exists() - - def platDir = new File( projectRoot , "${buildDir}/natives/$platform" ) - assert platDir.exists() - - def nativeFiles = platDir.listFiles() - assert nativeFiles.size() == libs.size() - assert nativeFiles.every { nf-> nf.name in libs } - } -} diff --git a/src/test/resources/lwjgl-platform-2.9.1-natives-linux.jar b/src/test/resources/lwjgl-platform-2.9.1-natives-linux.jar new file mode 100644 index 0000000..b0550cc Binary files /dev/null and b/src/test/resources/lwjgl-platform-2.9.1-natives-linux.jar differ diff --git a/src/test/resources/lwjgl-platform-2.9.1-natives-osx.jar b/src/test/resources/lwjgl-platform-2.9.1-natives-osx.jar new file mode 100644 index 0000000..92888ee Binary files /dev/null and b/src/test/resources/lwjgl-platform-2.9.1-natives-osx.jar differ diff --git a/src/test/resources/lwjgl-platform-2.9.1-natives-windows.jar b/src/test/resources/lwjgl-platform-2.9.1-natives-windows.jar new file mode 100644 index 0000000..f3f38e9 Binary files /dev/null and b/src/test/resources/lwjgl-platform-2.9.1-natives-windows.jar differ