I just want to share our j2objc setup with you guys, as it took us some time to fine-tune it in one of our projects. We're currently compiling 400+ files using j2objc, have classname duplicates in packages, have to be able to debug java code in xcode so that we can put breakpoints and step thru java code directly (not objc). The last tweak we made to improve build times saved us 50% of the build time on CI, which is a great help compared to the original (officially suggested) way of doing it.
We're building our j2objc part in a separate framework which is a part of the java library we're using. The main point is using a different approach to translate java to objC, and that's not using build rules, but a 'run script' build phase. Note that we really need to be able to debug java code inside xcode, so the external target approach also suggested by the j2objc team didn't work for us, and the build rule approach was really slowing us down.
We added the build phase just before the compile sources phase:
"${PROJECT_DIR}/scripts/j2objc.sh" "${PROJECT_DIR}" "${J2OBJC_HOME}" "${JAVA_SOURCE}";
j2objc.sh:
#!/bin/sh
if [ -z ${1} ]; then
echo "error: PROJECT_DIR is not set."
exit 1;
fi;
if [ -z ${2} ]; then
echo "error: J2OBJC_HOME is not set."
exit 1;
fi;
if [ -z ${3} ]; then
echo "error: JAVA_SOURCE is not set."
exit 1;
fi;
PROJECT_DIR=$1
J2OBJC_HOME=$2
JAVA_SOURCE=$3
if [ ! -f "${J2OBJC_HOME}/j2objc" ]; then
echo "J2OBJC_HOME is not correctly defined, currently set to '${J2OBJC_HOME}'";
exit 1;
fi;
SHOULD_COMPILE=$(find "${JAVA_SOURCE}" -name '*.java' | {
while read filename; do
JAVA_PATH="${filename}";
JAVA_RELATIVE_PATH=$(sed -e "s|^$JAVA_SOURCE||" <<< "${JAVA_PATH}");
BASE_RELATIVE_PATH=$(sed -e "s|.java$||" <<< "${JAVA_RELATIVE_PATH}");
BASE_ABSOLUTE_PATH="${JAVA_SOURCE}/${BASE_RELATIVE_PATH}";
H_PATH="${BASE_ABSOLUTE_PATH}.h";
M_PATH="${BASE_ABSOLUTE_PATH}.m";
if [ ! -f "${H_PATH}" ] || [ ! -f "${M_PATH}" ] || [ "${H_PATH}" -ot "${JAVA_PATH}" ] || [ "${M_PATH}" -ot "${JAVA_PATH}" ]; then
echo "1";
break;
fi;
done
}
)
if [ "$SHOULD_COMPILE" = "1" ]; then
"${J2OBJC_HOME}/j2objc" \
-d "${JAVA_SOURCE}" \
-sourcepath "${JAVA_SOURCE}" \
-classpath "${J2OBJC_HOME}/lib/jsr305-3.0.0.jar" \
--swift-friendly \
--strip-reflection \
--no-segmented-headers \
-use-arc \
--static-accessor-methods \
--nullability \
--prefixes "${PROJECT_DIR}/prefixes.properties" \
-g \
`find "${JAVA_SOURCE}" -name '*.java'`;
fi;
And you still have to leave the officially described build rule in your project, just remove the script from it so that it will only list the output files.
Ours look like this:
$(INPUT_FILE_DIR)/${INPUT_FILE_BASE}.h
$(INPUT_FILE_DIR)/${INPUT_FILE_BASE}.m
What you actually achieve using this approach is a lot faster j2objc translation, because your not executing a separate j2objc instance for each file to translate, but tell one j2objc instance to translate all your java files at once, and at the end, you just use the build rule to tell xcode where are the translated files so that they can be compiled. You should only add your java files to the project, not the objC ones, they will get compiled based on the build rule.
We're using a script to automatically generate the imports of the objC files into the framework, it's defined also in build phases, and has to be right after the compile sources build phase:
#!/bin/sh
PROJ_HEADER="${PROJECT_DIR}/Proj.h";
PROJ_TMP_HEADER="${PROJECT_DIR}/Proj.h.tmp";
HEADERS_PATH="${PROJECT_DIR}/../src/main/java/";
cat /dev/null > "${PROJ_TMP_HEADER}";
echo "// Generated by Proj external build target
#ifndef proj_h
#define proj_h
#import \"Proj/Bridge.h\"
" >> "${PROJ_TMP_HEADER}";
find "${PROJECT_DIR}/../src/main/java" -type f -name '*.h' | sed -e "s|^$HEADERS_PATH||" | sort | awk '{print "#import \"" $0 "\""}' >> "${PROJ_TMP_HEADER}";
echo "
#endif" >> "${PROJ_TMP_HEADER}";
FILE1=`cat "${PROJ_HEADER}" 2>/dev/null`;
FILE2=`cat "${PROJ_TMP_HEADER}"`;
if [ "$FILE1" = "$FILE2" ]; then
rm -f "${PROJ_TMP_HEADER}";
else
mv "${PROJ_TMP_HEADER}" "${PROJ_HEADER}";
fi;
The logic at the end is there so that we are not updating the header in every build, because the whole project would need to be recompiled then.
As you may have noticed we're storing our objC files next to java files to make the build rule possible to write as xcode lacks support to use some more sophisticated macros in output files, but it's not ideal, so we came up with a simple solution to make it easier to remove all the objc files from the directories when needed (e.g. when we need to re-add java files to the project because of some changes in the lib).
The solution is to delete the objc files when cleaning the project. You just need to create a new external build target, as that's the only way how you can perform something when the project gets cleaned. It's named 'external build system' in xcode 9.
And this is how it looks:
#! /bin/sh
if [ "${ACTION}" == "clean" ]
then
rm -f "${PROJECT_DIR}/Proj.h"
find "${PROJECT_DIR}/../src/main" -type f -name '*.h' -delete
find "${PROJECT_DIR}/../src/main" -type f -name '*.m' -delete
fi
You then need to add the clean target to the dependencies of your j2objc framework so that it's actually run when building and cleaning.