(for contributors, see CONTRIBUTING.md)
This FAQ only illustrates the most common ways to customize the J2ObjC Gradle Plugin. See J2objcConfig.groovy for comprehensive documentation of all options.
Table of Contents
- Start here for debugging (aka it's not working; aka don't panic)
- How do I develop with Xcode?
- How do I build against a specific version of OS X, iOS or watchOS?
- How can I speed up my build?
- What libraries are linked by default?
- How do I setup dependencies with J2ObjC?
- What should I add to .gitignore?
- What is the recommended folder structure for my app?
- What tasks does the plugin add to my project?
- How do I customize translation with J2ObjC?
- How do I customize compilation and linkage?
- Why is my clean build failing?
- How do I include Java files from additional source directories?
- How do I develop with Swift?
- How do I specify additional Xcode build configurations?
- How do I manually configure the Cocoapods Podfile?
- How do I manually configure my Xcode project to use the translated libraries?
- How do I update my J2ObjC translated code from Xcode?
- How do I work with Package Prefixes?
- How do I enable ARC for my translated Objective-C classes?
- How do I call finalConfigure()?
- Why is my Android build so much slower after adding J2ObjC?
- Error: implicit declaration of function 'JreRelease' is invalid in C99 [-Werror,-Wimplicit-function-declaration] JreRelease(this$0_)
- How do I disable a plugin task?
- Cycle Finder Basic Setup
- Cycle Finder Advanced Setup
- How do I develop on Windows or Linux?
- How do I fix missing required architecture linker warning?
- How do I fix Undefined symbols for architecture linker warning?
- How do I solve the Eclipse error message Obtaining Gradle model...?
Some common misconfigurations can cause numerous Gradle errors.
-
First make sure you have a supported version of j2objc and Xcode (see README.md for our current supported versions) installed locally, and that your J2OBJC_HOME property is set correctly to the distribution directory of j2objc. This is not the source repository root for j2objc. The distribution directory should have at minimum lib/ and include/ subdirectories. (If you download j2objc release .zip files from https://github.com/google/j2objc, you get only the distribution by default.)
-
Verify you are using the latest released version of the plugin. Forks of the plugin exist; if you are looking for help at https://github.com/j2objc-contrib/j2objc-contrib (the original), please use the latest version distributed from https://plugins.gradle.org/plugin/com.github.j2objccontrib.j2objcgradle.
-
If you are still getting errors regarding classes, executables, Jars, or libraries that cannot be found, and you've verified that your J2OBJC_HOME is set correctly, you may have stale Gradle caches, which can be cleared as follows. Note the following steps will cause you to rebuild everything, so the next build may take a long time.
# (from your Gradle project's root directory)
# Stops any Gradle daemons if they are running.
./gradlew --stop
# Remove cached Gradle database.
rm -rf .gradle/
# Remove cached Gradle outputs.
rm -rf build/
Now try building again.
The J2ObjC Gradle Plugin can configure your Xcode project with CocoaPods.
To take advantage of this, specify the directory that contains PROJECT.xcodeproj
as
the xcodeProjectDir
in your shared j2objcConfig
per the Quick Start Guide.
Gradle projects that shared
depends on must not specify xcodeProjectDir
so that only one project
controls the Xcode updates.
After running j2objcXcode
, open the .xcworkspace
file in Xcode. If the .xcodeproj
file
is opened in Xcode then CocoaPods will fail. This will appear as an Xcode build time error:
library not found for -lPods-IOS-APP-j2objc-shared
The above system uses CocoaPods and is the quickest way to build in Xcode with this plugin.
If you wish to avoid using CocoaPods, do not specify the xcodeProjectDir
option and instead
manually
add the static libraries and translated header directories to your Xcode project. The
j2objcXcode
task will not do anything.
Also see the FAQ note on developing with Swift.
For example, to use methods only available in iOS 9.2:
// File: shared/build.gradle
j2objcConfig {
minVersionIos '9.2'
...
}
The settings are minVersionIos, minVersionOsx & minVersionWatchos
. You can see the
defaults in J2objcConfig.groovy.
You can reduce the build time by 50% by skipping the release binaries by adding the
following to your root level local.properties
file:
# File: local.properties
j2objc.release.enabled=false
This is helpful for a tight modify-compile-test loop using only debug binaries.
You can also do this for j2objc.debug.enabled
.
If you'd rather just disable release builds for a particular run of the command line:
J2OBJC_RELEASE_ENABLED=false ./gradlew build
The local.properties
value overrides the environment variable, if present.
A number of standard libraries are included with the J2ObjC releases and linked by default when using the plugin. To add other libraries, see the FAQs about dependencies. The standard libraries are:
com.google.guava:guava
com.google.j2objc:j2objc-annotations
com.google.protobuf:protobuf-java
junit:junit (test only)
org.mockito:mockito-core (test only)
org.hamcrest:hamcrest-core (test only)
Note that this only covers the Objective-C libraries; if you want to use these libraries in your Java code, you must still include the standard dependency directives like:
// File: shared/build.gradle
dependencies {
compile 'com.google.guava:guava:18.0'
testCompile 'junit:junit:4.11'
}
The plugin can handle many types of dependencies: multiple Java projects, external libraries and unit-tests (with source), existing Objective-C code, etc.
See dependencies.md.
The .gitignore file should follow existing conventions for Android Studio and Xcode. Once that's configured, check that it contains the following:
# File: .gitignore
# Includes j2objc.home setting (Android Studio should have added this)
local.properties
# CocoaPods temporary files used for Xcode
Pods/
This is the suggested folder structure. It also shows a number of generated files and folders that aren't committed to your repository (marked with .gitignore). Files are shown before folders, so it is not in strict alphabetical order.
workspace
├── .gitignore // Add Ignores: local.properties, build/, Pod/
├── build.gradle
├── local.properties // sdk.dir=<Android SDK> and j2objc.home=<J2ObjC>, .gitignore
├── settings.gradle // include ':android', ':shared'
├── android
│ ├── build.gradle // dependencies { compile project(':shared') }
│ └── src/... // src/main/java/... and more, only Android specific code
├── ios
│ ├── IOS-APP.xcodeproj // Xcode project, which is modified by j2objcXcode / CocoaPods
│ ├── Podfile // j2objcXcode modifies this file for use by CocoaPods, committed
│ ├── WORKSPACE.xcworkspace // Xcode workspace
│ ├── IOS-APP/... // Xcode workspace
│ ├── IOS-APPTests/... // j2objcXcode configures as above but with "debug" buildType
│ └── Pods/... // temp files generated by CocoaPods for Xcode, .gitignore
└── shared
├── build.gradle // apply 'java' then 'j2objc' plugins
├── build // generated build directory, .gitignore
│ ├── ... // other build output
│ ├── binaries/... // Contains test binary: testJ2objcExecutable/debug/testJ2objc
│ ├── j2objc-shared.podspec // j2objcXcode generates these settings to modify Xcode
│ └── j2objcOutputs/... // j2objcAssemble copies libraries and headers here
├── lib // library dependencies, must have source code for translation
│ ├── lib-with-src.jar // library with source can be translated, see FAQ on how to use
│ └── libpreBuilt.a // library prebuilt for ios, see FAQ on how to use
└── src/... // src/main/java/... shared code for translation
The main tasks for the plugin are:
j2objcTranslate - Translates Java source to Objective-C
j2objcAssemble - Outputs built libraries, source & resources to 'build/j2objcOutputs'
j2objcTest - Runs all JUnit tests in the translated code
j2objcXcode - Updates Xcode project with translated libraries, headers & resources (uses CocoaPods)
j2objcBuild - Runs j2objcTranslate, j2objcAssemble, j2objcTest, and j2objcXcode
Running the build
task from the Gradle Java plugin will automatically run the j2objcBuild
task
and all the previous tasks (which it depends on). The j2objcXcode
task will disable itself
automatically if no xcodeProjectDir
is specified.
Note that you can use the Gradle shorthand of $ gradlew jA
to do the j2objcAssemble
task.
Inside your j2objcConfig
section, you can pass any of the options J2ObjC takes
with translateArgs
.
Make sure your arguments are separate strings, not a single space-concatenated string.
// File: shared/build.gradle
j2objcConfig {
// CORRECT
translateArgs '-use-arc'
translateArgs '-prefixes', 'file.prefixes'
// CORRECT
translateArgs '-use-arc', '-prefixes', 'file.prefixes'
// WRONG
translateArgs '-use-arc -prefixes file.prefixes'
...
}
Many well-known compiler or linker args can be customized using an existing
J2objcConfig
option. The plugin already sets up a standard build for you,
so only configure these options if the defaults fail to work.
As a last resort, inside your j2objcConfig
section, you can pass any of the
standard Clang compiler or linker args as extraObjcCompilerArgs
and extraLinkerArgs
,
respectively.
Make sure your arguments are separate strings, not a single space-concatenated strings
(see translateArgs
example above).
This is a known issue
if you don't have any JUnit tests. If you are doing ./gradlew clean build
, try instead
./gradlew clean && ./gradlew build
.
When you don't have any test source files, the plugin creates a placeholder to force the
creation of a test binary; this is done during project configuration phase, but clean
deletes
this file before build
can use it.
In order to include source files from sources different than src/main/java
you have to
modify the Java plugin's sourceSet(s).
For example, if you want to include files from src-gen/base
both into your JAR and (translated) into
your Objective C libraries, then add to your shared/build.gradle
:
// File: shared/build.gradle
sourceSets {
main {
java {
srcDir 'src-gen/base'
}
}
}
To work with Swift in Xcode, you need to configure a bridging header. Within that bridging header, include the files needed for using the JRE and any classes that you'd like to access from Swift code.
// File: ios/IOS-APP/IOS-APP-bridging-header.h
// J2ObjC requirement for Java Runtime Environment
// Included from /J2OBJC_HOME/include
#import "JreEmulation.h"
// Swift accessible J2ObjC translated classes
// Included from `shared/build/j2objcOutputs/src/main/objc`
#import "MyClassOne.h"
#import "MyClassTwo.h"
Set xcodeDebugConfigurations
and xcodeReleaseConfigurations
to specify which build
configurations should link the debug and release builds of the translated libs, respectively.
These default to ['Debug']
and ['Release']
which correspond to Xcode's default build
configuration names. Setting either of these to an empty array will omit the respective pod
line from the "pod method".
For example, if you have a build configuration called "Beta" for sending to internal testers and one called "Preview" for doing ad-hoc distribution:
// File: shared/build.gradle
j2objcConfig {
xcodeDebugConfigurations += ['Beta']
xcodeReleaseConfigurations += ['Preview']
...
}
The plugin will try to automatically update the Cocoapods Podfile but that may fail if the Podfile is too complex. In that situation, you can manually configure the pod method.
// File: shared/build.gradle
j2objcConfig {
xcodeTargetsManualConfig true
...
}
The "pod method" definition will still be added automatically (e.g.
def j2objc_shared...
). However, the "pod method" will not be added to any
targets, so that needs to be done manually. See example of the Podfile below:
// File: ios/Podfile
...
# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block
def j2objc_shared
pod 'j2objc-shared-debug', :configuration => ['Debug'], :path => '../shared/build/j2objcOutputs'
pod 'j2objc-shared-release', :configuration => ['Release'], :path => '../shared/build/j2objcOutputs'
end
# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END
<SOME COMPLEX RUBY>
...
# NOTE: this line must be added manually for the relevant targets:
j2objc_shared
...
end
To disable all modifications of the Podfile, disable the j2objcXcode
task.
This will also skip the pod install
step. The podspec files will still
be written (done by the j2objcPodspec
task).
// File: shared/build.gradle
j2objcConfig {
...
}
j2objcXcode {
enabled = false
}
Using CocoaPods is the quickest way to use the plugin. To configure Xcode manually, you will need to modify your Xcode project's build settings.
In each case, you need to make sure the specification of the path is relative to the location
of the Xcode project. We'll assume your J2ObjC distribution is at /J2OBJC_HOME
.
-
Add
/J2OBJC_HOME/include
andshared/build/j2objcOutputs/src/main/objc
toUSER_HEADER_SEARCH_PATHS
. -
Add
-lshared-j2objc
and-ljre_emul
toOTHER_LD_FLAGS
. If you also use Guava, JSR 305, etc., you will need to add appropriate OTHER_LD_FLAGS for them as well. -
For each build configuration and platform applicable to your project, add the following
LIBRARY_SEARCH_PATHS
:
- iOS Debug: /J2OBJC_HOME/lib, shared/build/j2objcOutputs/lib/iosDebug
- iOS Release: /J2OBJC_HOME/lib, shared/build/j2objcOutputs/lib/iosRelease
- OS X Debug: /J2OBJC_HOME/lib/macosx, shared/build/j2objcOutputs/lib/x86_64Debug
- OS X Release: /J2OBJC_HOME/lib/macosx, shared/build/j2objcOutputs/lib/x86_64Release
- Follow the instructions on "Build Phases" (only) here.
In each case, if the setting has existing values, append the ones above.
Wherever shared
appears above, duplicate that value for every J2ObjC project used,
including all transitive dependencies.
It is suggested to first develop and debug in Android, then after that is working to build in iOS. If you want to update your generated Objective-C code while working in Xcode, then you can add the J2ObjC build as an External Build System target:
- Select the main iOS project. File -> New Target -> Other -> External Build System.
Set the build tool as
/bin/sh
, and name the target j2objcGradle. - In the 'External Build Tool Configuration' screen that appears, set the following values:
- Build Tool:
/bin/sh
- Arguments:
./gradleJ2objcBuild.sh
- Directory: (the root Gradle project directory, where your settings.gradle file lives)
- Uncheck 'Pass build settings in environment'
- Add the following script named
gradleJ2objcBuild.sh
in your root Gradle project directory.
#!/bin/sh
# Don't contaminate the build with Xcode env vars.
unset DEVELOPER_DIR
# CUSTOMIZE: Add any needed paths here.
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
if [ "$ACTION" != "clean" ]; then
# CUSTOMIZE: List the j2objcBuild task for each J2ObjC project.
./gradlew shared:j2objcBuild -q
else
echo "Skipping Gradle Build for Clean"
fi
- Click on your your project in the left pane, select your main app target, and in 'Build Phases' select 'Target Dependencies'. Click the plus sign and find the j2objcGradle target added earlier.
From now on, building your iOS project will first update any J2ObjC code (if needed).
For the class com.example.dir.MyClass
, J2ObjC will by default translate the name to
ComExampleDirMyClass
, which is a long name to use. With package prefixes, you can shorten it
to something much more manageable, like CedMyClass
. See the example below on how to do this.
Also see the reference docs on package name prefixes.
// File: shared/build.gradle
j2objcConfig {
translateArgs '--prefixes', 'src/main/resources/prefixes.properties'
...
}
Storing prefixes.properties
in the resource folder will mean that it's copied in to the
Xcode build. Without having this file in the Xcode, calls to Class.forName("MyClass")
will fail for the mapped classes.
# File: shared/src/main/resources/prefixes.properties
com.example.dir: Ced
com.example.dir.subdir: Ced
You can also use wildcards in your prefixes file. This will map packages such as com.example.dir and com.example.dir.subdir both to Ced.
com.example.dir.*: Ced
Note: The use of ARC for the translated code is not recommended and is not required in order to use ARC for the hand-written Objective-C iOS app that links to the translated code.
Add the following to your configuration block. See here.
// File: shared/build.gradle
j2objcConfig {
translateArgs '-use-arc'
extraObjcCompilerArgs '-fobjc-arc'
...
}
You must always call finalConfigure()
at the end of j2objcConfig {...}
within your project's
build.gradle
file. You need to include an otherwise empty j2objcConfig {...}
block with this
call even if you do not need to customize any other j2objConfig
option.
// File: shared/build.gradle
j2objcConfig {
...
finalConfigure()
}
In addition, put your j2objcConfig
at the very end of your build.gradle. This allows
finalConfigure()
to correctly access other project settings, like dependencies.
Depending on your Android Studio 'run configuration', you may be building more of your Gradle
project than is neccessary. For example if your run configuration includes a general 'Make'
task before running, it will build all your gradle projects, including the j2objc parts.
Instead make sure your run configuration only performs the :android:assembleDebug
task:
At the command line, if you want to use the debug version of your Android app,
make sure you are running ./gradlew :android:assembleDebug
and not for example
./gradlew build
.
Error: implicit declaration of function 'JreRelease' is invalid in C99 [-Werror,-Wimplicit-function-declaration] JreRelease(this$0_)
See: How do I enable ARC for my translated Objective-C classes?
You can disable tasks performed by the plugin using the following configuration block in your
build.gradle
. This is separate and alongside the j2objcConfig
settings. For example, to
disable the j2objcTest
task, do the following:
// File: shared/build.gradle
j2objcConfig {
...
}
j2objcTest {
enabled = false
}
This task is disabled by default as it requires greater sophistication to use. It runs the
cycle_finder
tool in the J2ObjC release to detect memory cycles in your application.
Objects that are part of a memory cycle on iOS won't be deleted as it doesn't support
garbage collection.
The basic setup will implicitly check for 40 memory cycles - this is the expected number
of erroneous matches with jre_emul
library for J2ObjC version 0.9.6.1. This may cause
issues if this number changes with future versions of J2ObjC libraries.
// File: shared/build.gradle
j2objcCycleFinder {
enabled = true
}
Also see FAQ's Advanced Cycle Finder Setup.
This uses a specially annotated version of the jre_emul
source that marks all the
erroneously matched cycles such that they can be ignored. It requires downloading
and building the J2ObjC source:
-
Download the J2ObjC source to a directory (hereafter J2OJBC_REPO):
-
Within the J2OJBC_REPO, run:
(cd jre_emul && make java_sources_manifest)
-
Configure j2objcConfig in
shared/build.gradle
so CycleFinder uses the annotated J2ObjC source and whitelist. Note how this specifies an expected cycle count of zero, as the JRE cycles are already accounted for in the generated whitelist. If however, you have additional cycles you expect, you should use that number instead of zero.
// File: shared/build.gradle
j2objcConfig {
cycleFinderArgs '--whitelist', 'J2OBJC_REPO/jre_emul/cycle_whitelist.txt'
cycleFinderArgs '--sourcefilelist', 'J2OBJC_REPO/jre_emul/build_result/java_sources.mf'
cycleFinderExpectedCycles 0
...
}
For more details:
- http://j2objc.org/docs/Cycle-Finder-Tool.html
- https://groups.google.com/forum/#!msg/j2objc-discuss/2fozbf6-liM/R83v7ffX5NMJ
Windows and Linux support is limited to the j2objcCycleFinder
and j2objcTranslate
tasks.
Mac OS X is required for the j2objcAssemble, j2objcTest and j2objcXcode tasks
(see task descriptions) This matches the
J2ObjC Requirements.
It is recommended that Windows and Linux users use translateOnlyMode
to reduce the chances
of breaking the iOS / OS X build. This can be done by those developers configuring their
local.properties (the Mac OS X developers should not use this):
# File: local.properties
j2objc.translateOnlyMode=true
If you see a message similar to:
ld: warning: ignoring file /PATH/j2objcOutputs/lib/iosDebug/libPROJECT-j2objc.a,
missing required architecture i386 in file /PATH/j2objcOutputs/lib/iosDebug/libPROJECT-j2objc.a
(3 slices)
Undefined symbols for architecture i386:
"_OBJC_CLASS_$_ComExampleShared", referenced from:
type metadata accessor for ObjectiveC.ComExampleShared in ViewController.o
ld: symbol(s) not found for architecture i386
You are not building all the necessary architectures.
By default (for performance), we build only modern iOS device and simulator architectures. If you need i386 for older simulators (iPhone 5, 5c and earlier devices), add the following to your build.gradle file:
// File: shared/build.gradle
j2objcConfig {
supportedArchs += ['ios_i386']
...
}
This usually occurs with architecture x86_64
when using the simulator and
architecture arm64
when running on a device. The error should look
similar to this:
Undefined symbols for architecture x86_64:
"_OBJC_METACLASS_$_MyClassMethodName", referenced from:
_OBJC_METACLASS_$_MethodName in MyClass.o
It can usually be fixed by manually running the Cocoapods install command (adjust based on your Xcode directory):
(cd Xcode && pod install)
Note that re-running the build from Gradle as it will skip re-running the
j2objcXcode
task unless one of the dependencies has changed.
The problem occurs due to flakiness in Cocoapods. You can see this in the
linker output, it fails to include the -lshared-j2objc
library in the linker
flags. When working correctly, the linker flags should include the following:
-ObjC -lObjC -lshared-j2objc -lguava -licucore -ljavax_inject -ljre_emul -ljsr305 -lz
If you still have further issues, use the following commands to investigate the built libraries:
# list the architectures in the library. This should include "x86_64":
lipo -info shared/build/j2objcOutputs/lib/iosDebug/libshared-j2objc.a
# list out the methods in the file and look for the missing methods:
otool -SV base/build/j2objcOutputs/lib/iosDebug/libbase-j2objc.a
You have to first create the Gradle wrapper.
Go to your project folder and do gradle wrapper
. Refresh your Eclipse project.