diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b222151..ece279d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,20 @@ To see everything that has changed between version vA.B.C and vX.Y.Z, visit: https://github.com/j2objc-contrib/j2objc-gradle/compare/vA.B.C...vX.Y.Z -# Prelease Alphas +# Prerelease Alphas + +## vNext (HEAD) +Functionality: +* Automatic dependency resolution for Maven jars and Gradle projects #420 +* Proper limitation of functionality on non-Mac platforms #396 +* Embedded docs and versioning info for easier debugging #395 + +Code quality: +* Continuous integration on Mac #406 and on Windows #401 +* Added end to end tests on OSX (running j2objc) #409 #411 etc. +* Unit tests pass on Windows #404 +* Prevent publishing of bad releases #395 #398 +* Docs updates (various) ## v0.4.2-alpha Functionality: diff --git a/FAQ.md b/FAQ.md index 37a6437b..3ec0f652 100644 --- a/FAQ.md +++ b/FAQ.md @@ -31,6 +31,7 @@ Paste the results below, replacing existing contents. - [Error: implicit declaration of function 'JreRelease' is invalid in C99 [-Werror,-Wimplicit-function-declaration] JreRelease(this$0_)](#error-implicit-declaration-of-function-jrerelease-is-invalid-in-c99--werror-wimplicit-function-declaration-jrereleasethis0_) - [How do I disable a plugin task?](#how-do-i-disable-a-plugin-task) - [How do I setup dependencies with J2ObjC?](#how-do-i-setup-dependencies-with-j2objc) +- [How do I setup a dependency to a third-party Java library?](#how-do-i-setup-a-dependency-to-a-third-party-java-library) - [How do I setup a dependency on a Java project?](#how-do-i-setup-a-dependency-on-a-java-project) - [How do I setup a dependency on a prebuilt native library?](#how-do-i-setup-a-dependency-on-a-prebuilt-native-library) - [How do I setup a dependency on a native library project?](#how-do-i-setup-a-dependency-on-a-native-library-project) diff --git a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/DependencyConverter.groovy b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/DependencyConverter.groovy new file mode 100644 index 00000000..0ba1b747 --- /dev/null +++ b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/DependencyConverter.groovy @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2015 the authors of j2objc-gradle (see AUTHORS file) + * + * 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.github.j2objccontrib.j2objcgradle + +import groovy.transform.CompileStatic +import groovy.transform.PackageScope +import org.gradle.api.Project +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ExternalModuleDependency +import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.artifacts.SelfResolvingDependency + +/** + * Converts `[test]compile` dependencies to their + * `j2objcTranslation` and/or `j2objcLinkage` equivalents, depending on the type + * of dependency and whether or not they are already provided in native code. + *

+ * They will be resolved to appropriate `j2objc` constructs using DependencyResolver. + */ +@PackageScope +@CompileStatic +class DependencyConverter { + + final Project project + final J2objcConfig j2objcConfig + + // List of `group:name` + // TODO: Handle versioning. + static final List J2OBJC_DEFAULT_LIBS = [ + 'com.google.guava:guava', + 'junit:junit', + 'org.mockito:mockito-core', + 'com.google.j2objc:j2objc-annotations'] + + DependencyConverter(Project project, J2objcConfig j2objcConfig) { + this.project = project + this.j2objcConfig = j2objcConfig + } + + void configureAll() { + project.configurations.getByName('compile').dependencies.each { + visit(it) + } + project.configurations.getByName('testCompile').dependencies.each { + visit(it) + } + } + + protected void visit(Dependency dep) { + if (dep instanceof ProjectDependency) { + // ex. `compile project(':peer1')` + visitProjectDependency(dep as ProjectDependency) + } else if (dep instanceof SelfResolvingDependency) { + // ex. `compile fileTree(dir: 'libs', include: ['*.jar'])` + visitSelfResolvingDependency(dep as SelfResolvingDependency) + } else if (dep instanceof ExternalModuleDependency) { + // ex. `compile "com.google.code.gson:gson:2.3.1"` + visitExternalModuleDependency(dep as ExternalModuleDependency) + } else { + // Everything else + visitGenericDependency(dep) + } + } + + protected void visitSelfResolvingDependency( + SelfResolvingDependency dep) { + project.logger.debug("j2objc dependency converter: Translating file dep: $dep") + project.configurations.getByName('j2objcTranslation').dependencies.add( + dep.copy()) + } + + protected void visitProjectDependency(ProjectDependency dep) { + project.logger.debug("j2objc dependency converter: Linking Project: $dep") + project.configurations.getByName('j2objcLinkage').dependencies.add( + dep.copy()) + } + + protected void visitExternalModuleDependency(ExternalModuleDependency dep) { + project.logger.debug("j2objc dependency converter: External module dep: $dep") + // If the dep is already in the j2objc dist, ignore it. + if (J2OBJC_DEFAULT_LIBS.contains("${dep.group}:${dep.name}".toString())) { + // TODO: A more correct method might be converting these into our own + // form of SelfResolvingDependency that specifies which j2objc dist lib + // to use. + project.logger.debug("-- Skipped J2OBJC_DEFAULT_LIB: $dep") + return + } + project.logger.debug("-- Copied as source: $dep") + String group = dep.group == null ? '' : dep.group + String version = dep.version == null ? '' : dep.version + // TODO: Make this less fragile. What if sources don't exist for this artifact? + project.dependencies.add('j2objcTranslation', "${group}:${dep.name}:${version}:sources") + } + + protected void visitGenericDependency(Dependency dep) { + project.logger.warn("j2objc dependency converter: Unknown dependency type: $dep; copying naively") + project.configurations.getByName('j2objcTranslation').dependencies.add( + dep.copy()) + } +} diff --git a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/DependencyResolver.groovy b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/DependencyResolver.groovy new file mode 100644 index 00000000..9a5823e8 --- /dev/null +++ b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/DependencyResolver.groovy @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2015 the authors of j2objc-gradle (see AUTHORS file) + * + * 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.github.j2objccontrib.j2objcgradle + +import groovy.transform.CompileStatic +import groovy.transform.PackageScope +import org.gradle.api.InvalidUserDataException +import org.gradle.api.Project +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.artifacts.SelfResolvingDependency +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.tasks.bundling.AbstractArchiveTask + +/** + * Resolves `j2objcTranslation` and 'j2objcLinkage' dependencies into their `j2objc` constructs. + */ +@PackageScope +@CompileStatic +class DependencyResolver { + + final Project project + final J2objcConfig j2objcConfig + + DependencyResolver(Project project, J2objcConfig j2objcConfig) { + this.project = project + this.j2objcConfig = j2objcConfig + } + + void configureAll() { + project.configurations.getByName('j2objcTranslation').each { File it -> + // These are the resolved files, NOT the dependencies themselves. + visitTranslateFile(it) + } + project.configurations.getByName('j2objcLinkage').dependencies.each { + visitLink(it) + } + } + + protected void visitTranslateFile(File depFile) { + j2objcConfig.translateSourcepaths(depFile.absolutePath) + j2objcConfig.enableBuildClosure() + } + + protected void visitLink(Dependency dep) { + if (dep instanceof ProjectDependency) { + visitLinkProjectDependency((ProjectDependency) dep) + } else if (dep instanceof SelfResolvingDependency) { + visitLinkSelfResolvingDependency((SelfResolvingDependency) dep) + } else { + visitLinkGenericDependency(dep) + } + } + + protected void visitLinkSelfResolvingDependency( + SelfResolvingDependency dep) { + // TODO: handle native prebuilt libraries as files. + throw new UnsupportedOperationException("Cannot automatically link J2ObjC dependency: $dep") + } + + protected void visitLinkProjectDependency(ProjectDependency dep) { + Project beforeProject = dep.dependencyProject + // We need to have j2objcConfig on the beforeProject configured first. + project.evaluationDependsOn beforeProject.path + + if (!beforeProject.plugins.hasPlugin(JavaPlugin)) { + String message = "$beforeProject is not a Java project.\n" + + "dependsOnJ2ObjcLib can only automatically resolve a\n" + + "dependency on a Java project also converted using the\n" + + "J2ObjC Gradle Plugin." + throw new InvalidUserDataException(message) + } + + if (!beforeProject.plugins.hasPlugin(J2objcPlugin)) { + String message = "$beforeProject does not use the J2ObjC Gradle Plugin.\n" + + "dependsOnJ2objcLib can be used only with another project that\n" + + "itself uses the J2ObjC Gradle Plugin." + throw new InvalidUserDataException(message) + } + + // Build and test the java/objc libraries and the objc headers of + // the other project first. + // Since we assert the presence of the J2objcPlugin above, + // we are guaranteed that the java plugin, which creates the jar task, + // is also present. + project.tasks.getByName('j2objcPreBuild').dependsOn { + return [beforeProject.tasks.getByName('j2objcBuild'), + beforeProject.tasks.getByName('jar')] + } + AbstractArchiveTask jarTask = beforeProject.tasks.getByName('jar') as AbstractArchiveTask + project.logger.debug("$project:j2objcTranslate must use ${jarTask.archivePath}") + j2objcConfig.translateClasspaths += jarTask.archivePath.absolutePath + j2objcConfig.nativeCompilation.dependsOnJ2objcLib(beforeProject) + } + + protected void visitLinkGenericDependency(Dependency dep) { + throw new UnsupportedOperationException("Cannot automatically link J2ObjC dependency: $dep") + } +} diff --git a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfig.groovy b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfig.groovy index 47f28262..a526321c 100644 --- a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfig.groovy +++ b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfig.groovy @@ -15,17 +15,15 @@ */ package com.github.j2objccontrib.j2objcgradle - import com.github.j2objccontrib.j2objcgradle.tasks.Utils import com.google.common.annotations.VisibleForTesting import groovy.transform.CompileStatic +import groovy.transform.TypeCheckingMode import org.gradle.api.InvalidUserDataException import org.gradle.api.Project import org.gradle.api.Task -import org.gradle.api.tasks.bundling.AbstractArchiveTask import org.gradle.api.tasks.util.PatternSet import org.gradle.util.ConfigureUtil - /** * j2objcConfig is used to configure the plugin with the project's build.gradle. * @@ -184,6 +182,17 @@ class J2objcConfig { appendArgs(this.translateArgs, 'translateArgs', translateArgs) } + /** + * Enables --build-closure, which translates classes referenced from the + * list of files passed for translation, using the + * {@link #translateSourcepaths}. + */ + void enableBuildClosure() { + if (!translateArgs.contains('--build-closure')) { + translateArgs('--build-closure') + } + } + /** * Local jars for translation e.g.: "lib/json-20140107.jar", "lib/somelib.jar". * This will be added to j2objc as a '-classpath' argument. @@ -227,6 +236,27 @@ class J2objcConfig { // the build breaks, you need to do a clean build. boolean UNSAFE_incrementalBuildClosure = false + /** + * Experimental functionality to automatically configure dependencies. + * Consider you have dependencies like: + *

+     * dependencies {
+     *     compile project(':peer1')                  // type (1)
+     *     compile 'com.google.code.gson:gson:2.3.1'  // type (3)
+     *     compile 'com.google.guava:guava:18.0'      // type (2)
+     *     testCompile 'junit:junit:4.11'             // type (2)
+     * }
+     * 
+ * Project dependencies (1) will be added as a `j2objcLink` dependency. + * Libraries already included in j2objc (2) will be ignored. + * External libraries in Maven (3) will be added in source JAR form to + * `j2objcTranslate`, and translated using `--build-closure`. + * Dependencies must be fully specified before you call finalConfigure(). + *

+ * This will become the default when stable in future releases. + */ + boolean autoConfigureDeps = false + /** * Additional libraries that are part of the j2objc distribution. *

@@ -292,8 +322,11 @@ class J2objcConfig { /** * @see #dependsOnJ2objcLib(org.gradle.api.Project) + * @deprecated Use `dependencies { j2objcLinkage project(':beforeProjectName') }` or + * `autoConfigDeps = true` instead. */ // TODO: Do this automatically based on project dependencies. + @Deprecated void dependsOnJ2objcLib(String beforeProjectName) { dependsOnJ2objcLib(project.project(beforeProjectName)) } @@ -311,33 +344,18 @@ class J2objcConfig { * Do not also include beforeProject's java source or jar in the * translateSourcepaths or translateClasspaths, respectively. Calling this method * is preferable and sufficient. + * + * @deprecated Use `dependencies { j2objcLinkage project(':beforeProjectName') }` or + * `autoConfigDeps=true` instead. */ - // TODO: Do this automatically based on project dependencies. + // TODO: Phase this API out, and have J2ObjC-applied project dependencies controlled + // solely via `j2objcLink` configuration. + @CompileStatic(TypeCheckingMode.SKIP) + @Deprecated void dependsOnJ2objcLib(Project beforeProject) { - // We need to have j2objcConfig on the beforeProject configured first. - project.evaluationDependsOn beforeProject.path - - if (!beforeProject.plugins.hasPlugin(J2objcPlugin)) { - String message = "$beforeProject does not use the J2ObjC Gradle Plugin.\n" + - "dependsOnJ2objcLib can be used only with another project that\n" + - "itself uses the J2ObjC Gradle Plugin." - throw new InvalidUserDataException(message) + project.dependencies { + j2objcLinkage beforeProject } - - // Build and test the java/objc libraries and the objc headers of - // the other project first. - // Since we assert the presence of the J2objcPlugin above, - // we are guaranteed that the java plugin, which creates the jar task, - // is also present. - project.tasks.getByName('j2objcPreBuild').dependsOn { - return [beforeProject.tasks.getByName('j2objcBuild'), - beforeProject.tasks.getByName('jar')] - } - AbstractArchiveTask jarTask = beforeProject.tasks.getByName('jar') as AbstractArchiveTask - project.logger.debug("$project:j2objcTranslate must use ${jarTask.archivePath}") - translateClasspaths += jarTask.archivePath.absolutePath - - nativeCompilation.dependsOnJ2objcLib(beforeProject) } /** @@ -538,22 +556,47 @@ class J2objcConfig { protected boolean finalConfigured = false /** - * Configures the native build using. Must be called at the very + * Configures the j2objc build. Must be called at the very * end of your j2objcConfig block. */ - // TODO: When Gradle makes it possible to modify a native build config - // after initial creation, we can remove this, and have methods on this object - // mutate the existing native model { } block. See: - // https://discuss.gradle.org/t/problem-with-model-block-when-switching-from-2-2-1-to-2-4/9937 @VisibleForTesting void finalConfigure() { - nativeCompilation.apply(project.file("${project.buildDir}/j2objcSrcGen")) + validateConfiguration() + // Conversion of compile and testCompile dependencies occurs optionally. + if (autoConfigureDeps) { + convertDeps() + } + // Resolution of j2objcTranslateSource dependencies occurs always. + // This lets users turn off autoConfigureDeps but manually set j2objcTranslateSource. + resolveDeps() + configureNativeCompilation() + configureTaskState() finalConfigured = true + } + protected void validateConfiguration() { assert destLibDir != null assert destSrcMainDir != null assert destSrcTestDir != null + } + + protected void configureNativeCompilation() { + // TODO: When Gradle makes it possible to modify a native build config + // after initial creation, we can remove this, and have methods on this object + // mutate the existing native model { } block. See: + // https://discuss.gradle.org/t/problem-with-model-block-when-switching-from-2-2-1-to-2-4/9937 + nativeCompilation.apply(project.file("${project.buildDir}/j2objcSrcGen")) + } + + protected void convertDeps() { + new DependencyConverter(project, this).configureAll() + } + + protected void resolveDeps() { + new DependencyResolver(project, this).configureAll() + } + protected void configureTaskState() { // Disable only if explicitly present and not true. boolean debugEnabled = Boolean.parseBoolean(Utils.getLocalProperty(project, 'debug.enabled', 'true')) boolean releaseEnabled = Boolean.parseBoolean(Utils.getLocalProperty(project, 'release.enabled', 'true')) @@ -623,4 +666,12 @@ class J2objcConfig { } } } + + @VisibleForTesting + void testingOnlyPrepConfigurations() { + // When testing we don't always want to apply the entire plugin + // before calling finalConfigure. + project.configurations.create('j2objcTranslation') + project.configurations.create('j2objcLinkage') + } } diff --git a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcPlugin.groovy b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcPlugin.groovy index bfa22600..daf83412 100644 --- a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcPlugin.groovy +++ b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcPlugin.groovy @@ -85,6 +85,23 @@ class J2objcPlugin implements Plugin { // specified in j2objcConfig (or associated defaults in J2objcConfig). File j2objcSrcGenDir = file("${buildDir}/j2objcSrcGen") + // These configurations are groups of artifacts and dependencies for the plugin build + // https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.Configuration.html + configurations { + // When j2objcConfig.autoConfigureDeps is true, this configuration + // will have source paths automatically added to it. You can add + // *source* JARs/directories yourself as well. + j2objcTranslation { + description = 'J2ObjC Java source dependencies that need to be ' + + 'transitively translated via --build-closure' + } + // Currently, we can only handle Project dependencies already translated to Objective-C. + j2objcLinkage { + description = 'J2ObjC native library dependencies that need to be ' + + 'linked into the final library, and do not need translation' + } + } + // Produces a modest amount of output logging.captureStandardOutput LogLevel.INFO diff --git a/src/test/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfigTest.groovy b/src/test/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfigTest.groovy index 85b2d913..b4669481 100644 --- a/src/test/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfigTest.groovy +++ b/src/test/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfigTest.groovy @@ -80,6 +80,7 @@ class J2objcConfigTest { J2objcConfig ext = new J2objcConfig(proj) assert !ext.finalConfigured + ext.testingOnlyPrepConfigurations() ext.finalConfigure() assert ext.finalConfigured } @@ -91,6 +92,7 @@ class J2objcConfigTest { assert !ext.finalConfigured ext.translateOnlyMode = true + ext.testingOnlyPrepConfigurations() ext.finalConfigure() assert ext.finalConfigured } @@ -102,6 +104,7 @@ class J2objcConfigTest { assert !ext.finalConfigured ext.translateOnlyMode = true + ext.testingOnlyPrepConfigurations() ext.finalConfigure() assert ext.finalConfigured } @@ -116,6 +119,7 @@ class J2objcConfigTest { expectedException.expectMessage('Mac OS X is required for Native Compilation of translated code') assert !ext.finalConfigured + ext.testingOnlyPrepConfigurations() ext.finalConfigure() assert ext.finalConfigured } @@ -130,6 +134,7 @@ class J2objcConfigTest { expectedException.expectMessage('Mac OS X is required for Native Compilation of translated code') assert !ext.finalConfigured + ext.testingOnlyPrepConfigurations() ext.finalConfigure() assert ext.finalConfigured } diff --git a/src/test/groovy/com/github/j2objccontrib/j2objcgradle/MultiProjectTest.groovy b/src/test/groovy/com/github/j2objccontrib/j2objcgradle/MultiProjectTest.groovy index 0117af0b..85ca9cf1 100644 --- a/src/test/groovy/com/github/j2objccontrib/j2objcgradle/MultiProjectTest.groovy +++ b/src/test/groovy/com/github/j2objccontrib/j2objcgradle/MultiProjectTest.groovy @@ -45,6 +45,7 @@ class MultiProjectTest { @Test(expected = InvalidUserDataException) void twoProjectsWithDependsOnJ2objcLib_MissingPluginOnProject1() { j2objcConfig2.dependsOnJ2objcLib(proj1) + j2objcConfig2.finalConfigure() } @Test diff --git a/systemTests/multiProject1/extended/build.gradle b/systemTests/multiProject1/extended/build.gradle index 4102b325..c04325c8 100644 --- a/systemTests/multiProject1/extended/build.gradle +++ b/systemTests/multiProject1/extended/build.gradle @@ -25,11 +25,14 @@ dependencies { compile project(':base') // Intentionally testing e2e use of a built-in j2objc library, Guava. compile 'com.google.guava:guava:17.0' + // NOTE: this is an external dependency. The plugin automatically downloads the source, + // translates and links it in to the compiled library. No further configuration is required. + compile 'com.google.code.gson:gson:2.3.1' testCompile 'junit:junit:4.12' } j2objcConfig { - dependsOnJ2objcLib project(':base') + autoConfigureDeps true finalConfigure() } diff --git a/systemTests/multiProject1/extended/src/main/java/com/example/ExtendedCube.java b/systemTests/multiProject1/extended/src/main/java/com/example/ExtendedCube.java index 405df92d..87a9b0e6 100644 --- a/systemTests/multiProject1/extended/src/main/java/com/example/ExtendedCube.java +++ b/systemTests/multiProject1/extended/src/main/java/com/example/ExtendedCube.java @@ -18,6 +18,9 @@ import java.lang.Override; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + import com.google.common.base.Joiner; public class ExtendedCube extends Cube { @@ -28,7 +31,8 @@ public ExtendedCube(int dimension) { @Override public String toString() { - return String.format("[ExtendedCube %d]", dimension); + Gson gson = new GsonBuilder().create(); + return gson.toJson(this); } @Override diff --git a/systemTests/multiProject1/extended/src/test/java/com/example/ExtendedCubeTest.java b/systemTests/multiProject1/extended/src/test/java/com/example/ExtendedCubeTest.java index a79ffa03..b2722145 100644 --- a/systemTests/multiProject1/extended/src/test/java/com/example/ExtendedCubeTest.java +++ b/systemTests/multiProject1/extended/src/test/java/com/example/ExtendedCubeTest.java @@ -23,7 +23,7 @@ public class ExtendedCubeTest { @Test public void testToString() { - Assert.assertEquals("[ExtendedCube 7]", new ExtendedCube(7).toString()); + Assert.assertEquals("{\"dimension\":7}", new ExtendedCube(7).toString()); } @Test