最快的 j2objc 集成
Fastest possible j2objc integration
我只想与大家分享我们的 j2objc 设置,因为我们花了一些时间在我们的一个项目中对其进行微调。我们目前正在使用 j2objc 编译 400 多个文件,包中有重复的类名,必须能够调试 xcode 中的 java 代码,以便我们可以放置断点并单步执行 java 代码直接(不是 objc)。我们为缩短构建时间所做的最后一次调整在 CI 上为我们节省了 50% 的构建时间,与最初的(官方建议的)方法相比,这是一个很大的帮助。
我们正在一个单独的框架中构建我们的 j2objc 部分,该框架是我们正在使用的 java 库的一部分。要点是使用不同的方法将 java 转换为 objC,这不是使用构建规则,而是 'run script' 构建阶段。请注意,我们确实需要能够调试 xcode 内的 java 代码,因此 j2objc 团队也建议的外部目标方法对我们不起作用,构建规则方法确实减慢了我们的速度下来。
我们在编译源代码阶段之前添加了构建阶段:
"${PROJECT_DIR}/scripts/j2objc.sh" "${PROJECT_DIR}" "${J2OBJC_HOME}" "${JAVA_SOURCE}";
j2objc.sh:
#!/bin/sh
if [ -z ]; then
echo "error: PROJECT_DIR is not set."
exit 1;
fi;
if [ -z ]; then
echo "error: J2OBJC_HOME is not set."
exit 1;
fi;
if [ -z ]; then
echo "error: JAVA_SOURCE is not set."
exit 1;
fi;
PROJECT_DIR=
J2OBJC_HOME=
JAVA_SOURCE=
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;
而且您仍然需要在您的项目中保留官方描述的构建规则,只需从中删除脚本,这样它就只会列出输出文件。
我们的看起来像这样:
$(INPUT_FILE_DIR)/${INPUT_FILE_BASE}.h
$(INPUT_FILE_DIR)/${INPUT_FILE_BASE}.m
您使用这种方法实际实现的是更快的 j2objc 翻译,因为您没有为每个要翻译的文件执行单独的 j2objc 实例,而是告诉一个 j2objc 实例一次翻译所有 java 文件,最后,您只需使用构建规则告诉 xcode 翻译文件在哪里,以便可以编译它们。您应该只将 java 文件添加到项目中,而不是 objC 文件,它们将根据构建规则进行编译。
我们正在使用脚本自动生成 objC 文件导入到框架中,它也在构建阶段定义,并且必须在编译源构建阶段之后:
#!/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 \"" [=13=] "\""}' >> "${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;
最后的逻辑是这样的,因此我们不会在每次构建时都更新 header,因为那时整个项目都需要重新编译。
您可能已经注意到,我们将 objC 文件存储在 java 文件旁边,以便可以编写构建规则,因为 xcode 不支持在输出文件中使用一些更复杂的宏,但这并不理想,所以我们想出了一个简单的解决方案,以便在需要时更容易地从目录中删除所有 objc 文件(例如,当我们需要 re-add java 文件到项目时,因为库中的一些更改)。
解决方法是清理项目时删除objc文件。您只需要创建一个新的外部构建目标,因为这是您在清理项目时执行某些操作的唯一方式。它在 xcode 9.
中被命名为 'external build system'
这是它的样子:
#! /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
然后您需要将 clean 目标添加到 j2objc 框架的依赖项中,以便在构建和清理时它实际上是 运行。
我们使用了与您类似的方法。然而,目前我们正在使用我认为更好的方式:
- 我们有与您一样的 Java 独立框架。但是,我们创建了工具,它可以观察文件夹中 Java 文件的变化,并自动将它们转换为 *.m。它还会检查 header 是否随您更改,不需要重新编译整个项目
- 我们正在使用 Cocoapods 将这些生成的文件与 iOS 应用集成。我们包括 *.m 和 *.java 以便能够调试 java 代码。 *.java 的规则是空的,和你的类似。
这种在 *.java 更改时自动生成 *.m 的方法有一个巨大的优势 - 代码竞赛无需重新编译 xCode 中的项目。对于 XCode 我们只使用另一个 .m 文件,不需要使用脚本等,根据我们的经验,xCode 处理得更好。只认为我们必须做的是 运行 pod install 每当我们添加新的 *.java 文件或进行一些重构时。我们正在尝试自动调用 pod install,但现在我们手动调用它。
我们还实现了协议缓冲区的自动编译,因为我们的项目也需要它们。
该工具可在 npmjs - https://www.npmjs.com/package/j2objcworker
我只想与大家分享我们的 j2objc 设置,因为我们花了一些时间在我们的一个项目中对其进行微调。我们目前正在使用 j2objc 编译 400 多个文件,包中有重复的类名,必须能够调试 xcode 中的 java 代码,以便我们可以放置断点并单步执行 java 代码直接(不是 objc)。我们为缩短构建时间所做的最后一次调整在 CI 上为我们节省了 50% 的构建时间,与最初的(官方建议的)方法相比,这是一个很大的帮助。
我们正在一个单独的框架中构建我们的 j2objc 部分,该框架是我们正在使用的 java 库的一部分。要点是使用不同的方法将 java 转换为 objC,这不是使用构建规则,而是 'run script' 构建阶段。请注意,我们确实需要能够调试 xcode 内的 java 代码,因此 j2objc 团队也建议的外部目标方法对我们不起作用,构建规则方法确实减慢了我们的速度下来。
我们在编译源代码阶段之前添加了构建阶段:
"${PROJECT_DIR}/scripts/j2objc.sh" "${PROJECT_DIR}" "${J2OBJC_HOME}" "${JAVA_SOURCE}";
j2objc.sh:
#!/bin/sh
if [ -z ]; then
echo "error: PROJECT_DIR is not set."
exit 1;
fi;
if [ -z ]; then
echo "error: J2OBJC_HOME is not set."
exit 1;
fi;
if [ -z ]; then
echo "error: JAVA_SOURCE is not set."
exit 1;
fi;
PROJECT_DIR=
J2OBJC_HOME=
JAVA_SOURCE=
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;
而且您仍然需要在您的项目中保留官方描述的构建规则,只需从中删除脚本,这样它就只会列出输出文件。
我们的看起来像这样:
$(INPUT_FILE_DIR)/${INPUT_FILE_BASE}.h
$(INPUT_FILE_DIR)/${INPUT_FILE_BASE}.m
您使用这种方法实际实现的是更快的 j2objc 翻译,因为您没有为每个要翻译的文件执行单独的 j2objc 实例,而是告诉一个 j2objc 实例一次翻译所有 java 文件,最后,您只需使用构建规则告诉 xcode 翻译文件在哪里,以便可以编译它们。您应该只将 java 文件添加到项目中,而不是 objC 文件,它们将根据构建规则进行编译。
我们正在使用脚本自动生成 objC 文件导入到框架中,它也在构建阶段定义,并且必须在编译源构建阶段之后:
#!/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 \"" [=13=] "\""}' >> "${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;
最后的逻辑是这样的,因此我们不会在每次构建时都更新 header,因为那时整个项目都需要重新编译。
您可能已经注意到,我们将 objC 文件存储在 java 文件旁边,以便可以编写构建规则,因为 xcode 不支持在输出文件中使用一些更复杂的宏,但这并不理想,所以我们想出了一个简单的解决方案,以便在需要时更容易地从目录中删除所有 objc 文件(例如,当我们需要 re-add java 文件到项目时,因为库中的一些更改)。
解决方法是清理项目时删除objc文件。您只需要创建一个新的外部构建目标,因为这是您在清理项目时执行某些操作的唯一方式。它在 xcode 9.
中被命名为 'external build system'这是它的样子:
#! /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
然后您需要将 clean 目标添加到 j2objc 框架的依赖项中,以便在构建和清理时它实际上是 运行。
我们使用了与您类似的方法。然而,目前我们正在使用我认为更好的方式:
- 我们有与您一样的 Java 独立框架。但是,我们创建了工具,它可以观察文件夹中 Java 文件的变化,并自动将它们转换为 *.m。它还会检查 header 是否随您更改,不需要重新编译整个项目
- 我们正在使用 Cocoapods 将这些生成的文件与 iOS 应用集成。我们包括 *.m 和 *.java 以便能够调试 java 代码。 *.java 的规则是空的,和你的类似。
这种在 *.java 更改时自动生成 *.m 的方法有一个巨大的优势 - 代码竞赛无需重新编译 xCode 中的项目。对于 XCode 我们只使用另一个 .m 文件,不需要使用脚本等,根据我们的经验,xCode 处理得更好。只认为我们必须做的是 运行 pod install 每当我们添加新的 *.java 文件或进行一些重构时。我们正在尝试自动调用 pod install,但现在我们手动调用它。
我们还实现了协议缓冲区的自动编译,因为我们的项目也需要它们。
该工具可在 npmjs - https://www.npmjs.com/package/j2objcworker