Сборка libGDX из исходного кода

Перед сборкой исходников libGDX, убедитесь, что установлены все компоненты, необходимые в требованиях.

Простой путь

Система сборки libGDX основана на Ant. Различные Ant скрипты отвечают за сборку разных частей libGDX.

Основной сценарий сборки называется build.xml и находиться в корневой директории libGDX репозитория. Он собирает дистрибутив libGDX, включая core API, back-end, все расширения и Javadoc. Чтобы построить дистрибутив выполните следующие команды в shell.

ant -f fetch.xml
ant

Этим самым загрузятся последние версии нативных библиотек (скомпилированный C/C++ код для всех платформ) из сервера сборки, так что вам не нужно делать их сборку самостоятельно. Затем будет вызван основной сценарий для сборки всех Java частей.

Конечным результатом будет zip архив с названием libgdx-версия.zip и директория под названием dist, содержащая распакованный контент архива, который будет, по сути, таким же, как и тот, который можно загрузить с nightly сервера сборки, плюс изменения, сделанные вами в исходниках.

Файл build.xml имеет target для каждого модуля. Каждая target настраивает несколько параметров (classpath, конечная директория и так далее), которые затем передаются на вход build-template.xml файлу. Файл build-template.xml отвечает за компиляцию исходного Java кода, а также исходников нативного кода. Последнее делается вызовом Ant сценария, называемого build.xml и находящегося в jni директории модуля. Если хотите использовать данный метод для компилирования нативного кода, то это не удастся. Как скомпилировать нативные исходники смотрите информацию ниже.

Сложный путь

Существует несколько проблем при сборке исходников нативного кода libGDX:

  • C/C++ код может быть доступен из Java только через JNI, для которого есть специальное решение, называемое gdx-jnigen.
  • Нам нужна сборка для различных платформ, Windows (32/64 бит), Linux (32/64 бит), Mac OS X (32/64 бит), Android(arm6/arm7) и iOS(i386/arm7).
  • Mac OS X и iOS сборки могут быть выполнены только на Mac.
  • В дополнение к компиляции нативного C/C++ нам нужно конвертировать различные jar файлы в .Net assemblies с помощью IKVM для iOS.

Давайте обсудим каждую часть, прежде чем погружаться в настройку цепи средств кросскомпиляции и посмотрим на процесс компиляции.

Jnigen

Jnigen - это расширение, которое позволяет поместить C/C++ код прямо в исходники Java кода. Это повышает локальность кода, которая концептуально относиться друг к другу (нативные методы Java класса и фактическая реализация) и делает рефакторинг кода намного проще по сравнению с обычным процессом JNI.

jnigen имеет две функции:

  • Проверять исходные Java файлы определенной директории, обнаруживать нативные методы и прилагаемую C++ реализацию и отбрасывать исходные C++ файлы и заголовки, схожа как бы вы делали вручную с помощью JNI.
  • Обеспечивать генерацию Ant сценариев сборки, которые собирают нативные исходники для каждой платформы.

Генерация нативного кода

Вот пример из смешивания Java/C++ в один Java исходный файл понятный для jnigen.

private static native ByteBuffer newDisposableByteBuffer (int numBytes); /*
   char* ptr = (char*)malloc(numBytes);
   return env->NewDirectByteBuffer(ptr, numBytes);
*/

private native static void copyJni (float[] src, Buffer dst, int numFloats, int offset); /*
   memcpy(dst, src + offset, numFloats << 2 );
*/

Код C++ содержится в блоке комментария после описания нативного Java метода. Со стороны Java вводятся параметры, которые будут доступны для C++ кода по их Java именам, и если возможно преобразование для ограниченного подмножества типов. Происходит следующее преобразование:

  • Примитивные типы передаются как есть, используются jint, jshort, jboolean как их типы.
  • Одномерные массивы примитивных типов преобразуются в типизированные указатели с прямым доступом. Массивы автоматически блокируются и разблокируются через JNIEnv::GetPrimitiveArrayCritical и JNIEnv::ReleasePrimitiveArrayCritical.
  • Прямые буферы конвертируются в unsigned char* указатели через JNIEnv::GetDirectBufferAddress. Следует отметить, что позиция буфера не учитывается.
  • Любой другой тип будет передан с соответствующим ему JNI типом, например обычные Java объекты будут переданы как объекты.

Вышеуказанные два jnigen нативных метода будут транслированы в следующий C++ исходный код (будет соответствующий и header файл).

JNIEXPORT jobject JNICALL Java_com_badlogic_gdx_utils_BufferUtils_newDisposableByteBuffer(JNIEnv* env, jclass clazz, jint numBytes) {
//@line:334
   char* ptr = (char*)malloc(numBytes);
   return env->NewDirectByteBuffer(ptr, numBytes);
}

JNIEXPORT void JNICALL Java_com_badlogic_gdx_utils_BufferUtils_copyJni___3FLjava_nio_Buffer_2II(JNIEnv* env, jclass clazz, jfloatArray obj_src, jobject obj_dst, jint numFloats, jint offset) {
   unsigned char* dst = (unsigned char*)env->GetDirectBufferAddress(obj_dst);
   float* src = (float*)env->GetPrimitiveArrayCritical(obj_src, 0);


//@line:348
   memcpy(dst, src + offset, numFloats << 2 );

   env->ReleasePrimitiveArrayCritical(obj_src, src, 0);
}

Как вы можете видеть в copyJni(), преобразование вставляется в верхнюю и нижнюю часть метода автоматически. Если вы вернетесь из JNI метода в другое место, отличное от конца вашего метода, то jnigen обернет вашу функцию со второй функцией, которая делает все преобразование, подобно здесь: Перевод исходников Java и C++.

Jnigen также выводит номера строк, говоря, где в исходном Java файле появился C++. Это полезно, если собираем генерируемый jnigen C++ код, так как сценарий Ant не показывает ошибки номером строк кода в Java, на которую можно перейти, нажав на строку в консоли.

Для того чтобы позволить Jnigen пройтись по исходному коду и сгенерировать C/C++ заголовки и исходные файлы, выполните следующие действия:

new NativeCodeGenerator().generate("src", "bin", "jni", new String[] {"**/*"}, null);

Укажите директорию с исходниками, директорию, содержащую скомпилированные .class файлы Java классов, файлы которые вы хотите включить и исключить. Смотрите NativeCodeGenerator для дополнительной информации.

Генерация сценария сборки

Как только были сгенерированны нативные файлы, хочется создать сценарий сборки для всех поддерживаемых платформ. В настоящий момент включена поддержка Windows (32/64-бит), Linux (32/64-бит), Mac OS X (x86, 32/64-бит), Android (arm6/arm7) and iOS (i386, arm7). Генератор сценария сборки jnigen имеет шаблонный Ant сценарий, который может быть параметризован для каждой платформы. Параметры задаются через BuiltTarget. Можно создать BuiltTarget для конкретной платформы подобным образом:

BuildTarget linux32 = BuildTarget.newDefaultTarget(TargetOS.Linux, false);

Это создает target сборку по умолчанию для Linux 32-бит. Можно добавить к BuildTarget дополнительные параметры для конкретной платформы. Повторите этот процесс для других BuildTarget.

Как только все target настроены, поместите их вместе в BuildConfig. Укажите имя shared/static библиотеки, например gdx, которая будет в итоге gdx.dll в Windows, libgdx.so в Linux и Android, libgdx.dylib в Mac OS X и libgdx.a в iOS. Так же можно указать какие файлы будут в итоге и так далее. Самый простой способ использования конфигурации выглядит следующим образом:

BuildConfig config = new BuildConfig("gdx");

Когда все target и настройки находятся в одном месте, настает время генерации Ant сценария при помощи AntScriptGenerator.

new AntScriptGenerator().generate(config, linux32, linux64, windows32, windows64, macosx, android, ios)