diff --git a/.appveyor/Build.sh b/.appveyor/Build.sh index 23d3fe07..56fd62d5 100755 --- a/.appveyor/Build.sh +++ b/.appveyor/Build.sh @@ -1,21 +1,22 @@ #!/usr/bin/env bash set -ex -# Creating flatpak directories -mkdir -p "${APPVEYOR_BUILD_FOLDER}/.flatpak/lib" "${APPVEYOR_BUILD_FOLDER}/.flatpak/data" "${APPVEYOR_BUILD_FOLDER}/.flatpak/bin" - +# Configure cd "$APPVEYOR_BUILD_FOLDER/src_rebuild" - ./premake5 gmake2 - cd project_gmake2_linux +# Build for config in debug_x86 release_x86 release_dev_x86 do make config=$config -j$(nproc) done -find ${APPVEYOR_BUILD_FOLDER}/src_rebuild/bin -name 'REDRIVER2*' -exec cp -t ${APPVEYOR_BUILD_FOLDER}/.flatpak/bin {} + +cd ${APPVEYOR_BUILD_FOLDER} + +# Creating flatpak directories +mkdir -p "${APPVEYOR_BUILD_FOLDER}/.flatpak/lib" "${APPVEYOR_BUILD_FOLDER}/.flatpak/data" "${APPVEYOR_BUILD_FOLDER}/.flatpak/bin" +find ${APPVEYOR_BUILD_FOLDER}/src_rebuild/bin/Release -name 'REDRIVER2*' -exec cp -t ${APPVEYOR_BUILD_FOLDER}/.flatpak/bin {} + # Copy missing libraries in the runtime for lib in libjpeg libopenal libsndio libbsd @@ -24,7 +25,6 @@ do done cp -r "${APPVEYOR_BUILD_FOLDER}/data" "${APPVEYOR_BUILD_FOLDER}/.flatpak/" -cd ${APPVEYOR_BUILD_FOLDER} # Editing metadatas with the current version export APPVEYOR_BUILD_DATE=$(date "+%Y-%m-%d") diff --git a/.appveyor/Install.sh b/.appveyor/Install.sh index afe2e976..7ca639f0 100755 --- a/.appveyor/Install.sh +++ b/.appveyor/Install.sh @@ -26,7 +26,7 @@ export XDG_DATA_DIRS="/var/lib/flatpak/exports/share:${HOME}/.local/share/flatpa flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo # Adding Platform/SDK for the Linux flatpak release -flatpak --user install flathub org.freedesktop.Platform/x86_64/20.08 -y -flatpak --user install flathub org.freedesktop.Sdk/x86_64/20.08 -y -flatpak --user install flathub org.freedesktop.Sdk.Compat.i386/x86_64/20.08 -y -flatpak --user install flathub org.freedesktop.Sdk.Extension.toolchain-i386/x86_64/20.08 -y +flatpak --user install flathub org.freedesktop.Platform/x86_64/22.08 -y +flatpak --user install flathub org.freedesktop.Sdk/x86_64/22.08 -y +flatpak --user install flathub org.freedesktop.Sdk.Compat.i386/x86_64/22.08 -y +flatpak --user install flathub org.freedesktop.Sdk.Extension.toolchain-i386/x86_64/22.08 -y diff --git a/.flatpak/io.github.opendriver2.Redriver2.appdata.xml b/.flatpak/io.github.opendriver2.Redriver2.appdata.xml index 52950d8f..c08e84b9 100644 --- a/.flatpak/io.github.opendriver2.Redriver2.appdata.xml +++ b/.flatpak/io.github.opendriver2.Redriver2.appdata.xml @@ -4,9 +4,9 @@ MIT MIT REDRIVER2 - Driver 2 Playstation game reverse engineering effort + Driver 2 - Back On The Streets / The Wheelman Is Back -

Driver 2 Playstation game reverse engineering effort

+

Reverse-Engineered version of Driver 2. https://opendriver2.github.io

Game diff --git a/.flatpak/ld.so.conf b/.flatpak/ld.so.conf new file mode 100644 index 00000000..76daf657 --- /dev/null +++ b/.flatpak/ld.so.conf @@ -0,0 +1,5 @@ +# We just make any GL32 extension have higher priority +include /run/flatpak/ld.so.conf.d/app-*-org.freedesktop.Platform.GL32.*.conf +/app/lib32 +/app/lib/i386-linux-gnu +/lib64 \ No newline at end of file diff --git a/.flatpak/start.sh b/.flatpak/start.sh index d268502f..7d0019d4 100644 --- a/.flatpak/start.sh +++ b/.flatpak/start.sh @@ -13,7 +13,7 @@ function importDefaultData { } if [ ! -d /var/data/DRIVER2 ]; then - zenity --error --no-wrap --text="`printf "DRIVER2 files are missing! Add the folder in:\n ${HOME}/.var/io.github.opendriver2.Redriver2/data"`" + zenity --error --no-wrap --text="`printf "DRIVER2 files are missing! Please provide DRIVER2 folder in:\n ${HOME}/.var/app/io.github.opendriver2.Redriver2/data"`" exit 0 fi diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f6462a3b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +FROM fedora:37 +LABEL Description="Build environment" + +ENV HOME /root + +SHELL ["/bin/bash", "-c"] + +RUN dnf update -y && \ + dnf groupinstall -y 'Development Tools' && \ + dnf install -y make gcc gcc-c++ \ + libjpeg-turbo-devel.i686 \ + glibc-devel.i686 \ + SDL2-devel.i686 \ + openal-soft-devel.i686 \ + flatpak flatpak-builder + +ENV APPVEYOR_BUILD_FOLDER=/src + +# Setting XDG_DATA_DIRS environement variable for flatpak +ENV XDG_DATA_DIRS="/var/lib/flatpak/exports/share:${HOME}/.local/share/flatpak/exports/share:$XDG_DATA_DIRS" + +# Adding the flathub repo +RUN flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + +# Adding Platform/SDK for the Linux flatpak release +RUN flatpak --user install flathub org.freedesktop.Platform/x86_64/22.08 -y +RUN flatpak --user install flathub org.freedesktop.Sdk/x86_64/22.08 -y +RUN flatpak --user install flathub org.freedesktop.Sdk.Compat.i386/x86_64/22.08 -y +RUN flatpak --user install flathub org.freedesktop.Sdk.Extension.toolchain-i386/x86_64/22.08 -y + +WORKDIR /src + +CMD ["/bin/bash"] + +# Building example: +# docker build -t builder/multiarch_build:1.0 -f Dockerfile . + +# Running example: +# docker run -it --privileged=true --rm --name=builder --mount type=bind,source=${PWD},target=/src builder/multiarch_build:1.0 bash diff --git a/appveyor.yml b/appveyor.yml index 50e4b803..876327b5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 7.6.{build} +version: 7.8.{build} branches: only: diff --git a/data/DRIVER2/GFX/HQ/ariblk.ttf b/data/DRIVER2/GFX/HQ/ariblk.ttf deleted file mode 100644 index e7ae345a..00000000 Binary files a/data/DRIVER2/GFX/HQ/ariblk.ttf and /dev/null differ diff --git a/data/DRIVER2/GFX/HQ/fefont.fn2 b/data/DRIVER2/GFX/HQ/fefont.fn2 new file mode 100644 index 00000000..9349c4d8 Binary files /dev/null and b/data/DRIVER2/GFX/HQ/fefont.fn2 differ diff --git a/data/DRIVER2/GFX/HQ/fefont.tga b/data/DRIVER2/GFX/HQ/fefont.tga new file mode 100644 index 00000000..89b1bb79 Binary files /dev/null and b/data/DRIVER2/GFX/HQ/fefont.tga differ diff --git a/data/DRIVER2/GFX/HQ/font2.fn2 b/data/DRIVER2/GFX/HQ/font2.fn2 new file mode 100644 index 00000000..313289be Binary files /dev/null and b/data/DRIVER2/GFX/HQ/font2.fn2 differ diff --git a/data/DRIVER2/GFX/HQ/font2.tga b/data/DRIVER2/GFX/HQ/font2.tga new file mode 100644 index 00000000..1bfe5c96 Binary files /dev/null and b/data/DRIVER2/GFX/HQ/font2.tga differ diff --git a/dockerbuild.sh b/dockerbuild.sh new file mode 100755 index 00000000..6158a1a1 --- /dev/null +++ b/dockerbuild.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -ex + +docker build -t builder/multiarch_build:1.0 -f Dockerfile . +docker run -it --privileged=true --rm --name=builder --mount type=bind,source=${PWD},target=/src builder/multiarch_build:1.0 ./.appveyor/Build.sh diff --git a/io.github.opendriver.redriver2.yaml b/io.github.opendriver.redriver2.yaml index 2fa0acf1..91a6d90f 100644 --- a/io.github.opendriver.redriver2.yaml +++ b/io.github.opendriver.redriver2.yaml @@ -1,9 +1,11 @@ ---- app-id: io.github.opendriver2.Redriver2 runtime: org.freedesktop.Platform -runtime-version: '20.08' -rename-icon: 'icon' +runtime-version: &runtime-version '22.08' +x-gl-version: &gl-version '1.4' +x-gl-versions: &gl-versions 22.08;22.08-extra;1.4 sdk: org.freedesktop.Sdk +separate-locales: false +rename-icon: 'icon' command: start.sh finish-args: - "--socket=x11" @@ -14,47 +16,66 @@ finish-args: - "--persist=." - "--allow=multiarch" - "--env=SDL_DYNAMIC_API=/app/lib/i386-linux-gnu/libSDL2-2.0.so.0" + add-extensions: org.freedesktop.Platform.Compat.i386: directory: lib/i386-linux-gnu - version: '20.08' + version: *runtime-version + org.freedesktop.Platform.Compat.i386.Debug: directory: lib/debug/lib/i386-linux-gnu - version: '20.08' + version: *runtime-version no-autodownload: true + org.freedesktop.Platform.GL32: directory: lib/i386-linux-gnu/GL - version: '20.08' + version: *gl-version + versions: *gl-versions subdirectories: true no-autodownload: true autodelete: false add-ld-path: lib - merge-dirs: vulkan/icd.d;glvnd/egl_vendor.d + merge-dirs: vulkan/icd.d;glvnd/egl_vendor.d;OpenCL/vendors;lib/dri;lib/d3d;vulkan/explicit_layer.d;vulkan/implicit_layer.d download-if: active-gl-driver enable-if: active-gl-driver + sdk-extensions: - org.freedesktop.Sdk.Compat.i386 - org.freedesktop.Sdk.Extension.toolchain-i386 + build-options: - prepend-pkg-config-path: "/app/lib32/pkgconfig:/usr/lib/i386-linux-gnu/pkgconfig" - ldflags: "-L/app/lib32" - append-path: "/usr/lib/sdk/toolchain-i386/bin" + prepend-pkg-config-path: /app/lib32/pkgconfig:/usr/lib/i386-linux-gnu/pkgconfig + ldflags: -L/app/lib32 + prepend-path: /usr/lib/sdk/toolchain-i386/bin env: CC: i686-unknown-linux-gnu-gcc CXX: i686-unknown-linux-gnu-g++ - libdir: "/app/lib32" + libdir: /app/lib32 + cleanup: - "/include" + modules: -- name: ld-i386 + +- name: platform-bootstrap buildsystem: simple build-commands: - - mkdir -p /app/lib/i386-linux-gnu /app/lib/debug/lib/i386-linux-gnu - - install -Dm644 -t /app/etc ld.so.conf + - | + set -e + mkdir -p /app/bin + mkdir -p /app/lib/i386-linux-gnu + mkdir -p /app/lib/debug/lib/i386-linux-gnu + mkdir -p /app/lib/i386-linux-gnu/GL + cp /usr/bin/addr2line /app/bin/ + cp /usr/lib/x86_64-linux-gnu/libbfd-*.so /app/lib/ + install -Dm644 -t /app/etc ld.so.conf + mkdir -p /app/links/lib + ln -srv /app/lib /app/links/lib/x86_64-linux-gnu + ln -srv /app/lib32 /app/links/lib/i386-linux-gnu sources: - - type: shell - commands: - - echo "/app/lib32" > ld.so.conf + - type: dir + path: .flatpak + - name: game buildsystem: simple build-commands: diff --git a/src_rebuild/Game/C/cars.c b/src_rebuild/Game/C/cars.c index acb0257f..56ae3672 100644 --- a/src_rebuild/Game/C/cars.c +++ b/src_rebuild/Game/C/cars.c @@ -253,7 +253,7 @@ void plotCarPolyGT3(int numTris, CAR_POLY *src, SVECTOR *vlist, SVECTOR *nlist, ofse = pg->damageLevel[src->originalindex]; - *(u_int*)&prim->u0 = (src->clut_uv0 & 0xffffU | pg->pciv_clut[palette + (src->clut_uv0 >> 0x10)] << 0x10) + ofse; + *(u_int*)&prim->u0 = pg->pciv_clut[(src->clut_uv0 >> 0x10) + palette] << 0x10 | (src->clut_uv0 & 0xffff) + ofse; *(u_int*)&prim->u1 = src->tpage_uv1 + ofse; *(u_int*)&prim->u2 = src->uv3_uv2 + ofse; @@ -330,7 +330,7 @@ void plotCarPolyGT3Lit(int numTris, CAR_POLY* src, SVECTOR* vlist, SVECTOR* nlis ofse = pg->damageLevel[src->originalindex]; - *(u_int*)&prim->u0 = (src->clut_uv0 & 0xffffU | pg->pciv_clut[palette + (src->clut_uv0 >> 0x10)] << 0x10) + ofse; + *(u_int*)&prim->u0 = pg->pciv_clut[(src->clut_uv0 >> 0x10) + palette] << 0x10 | (src->clut_uv0 & 0xffff) + ofse; *(u_int*)&prim->u1 = src->tpage_uv1 + ofse; *(u_int*)&prim->u2 = src->uv3_uv2 + ofse; @@ -407,7 +407,7 @@ void plotCarPolyGT3nolight(int numTris, CAR_POLY *src, SVECTOR *vlist, plotCarGl ofse = pg->damageLevel[src->originalindex]; - *(u_int*)&prim->u0 = (src->clut_uv0 & 0xffffU | pg->pciv_clut[palette + (src->clut_uv0 >> 0x10)] << 0x10) + ofse; + *(u_int*)&prim->u0 = pg->pciv_clut[(src->clut_uv0 >> 0x10) + palette] << 0x10 | (src->clut_uv0 & 0xffff) + ofse; *(u_int*)&prim->u1 = src->tpage_uv1 + ofse; *(u_int*)&prim->u2 = src->uv3_uv2 + ofse; @@ -965,7 +965,8 @@ void buildNewCars(void) // [D] [T] void buildNewCarFromModel(CAR_MODEL *car, MODEL *model, int first) { - u_char ptype, clut; + ushort clut; + u_char ptype, carid; u_char *polyList; CAR_POLY *cp; @@ -1074,12 +1075,14 @@ void buildNewCarFromModel(CAR_MODEL *car, MODEL *model, int first) { POLYGT3* pgt3 = (POLYGT3*)polyList; - clut = GetCarPalIndex(pgt3->texture_set); - civ_clut[clut][pgt3->texture_id][0] = texture_cluts[pgt3->texture_set][pgt3->texture_id]; + carid = GetCarPalIndex(pgt3->texture_set); + clut = (carid - 1) * 6 * 32 + pgt3->texture_id * 6; + + civ_clut[carid][pgt3->texture_id][0] = texture_cluts[pgt3->texture_set][pgt3->texture_id]; cp->vindices = M_INT_4R(pgt3->v0, pgt3->v1, pgt3->v2, 0); cp->nindices = M_INT_4R(pgt3->n0, pgt3->n1, pgt3->n2, 0); - cp->clut_uv0 = M_INT_2((clut * 384 + pgt3->texture_id * 12 - 384) >> 1, * (ushort*)&pgt3->uv0); + cp->clut_uv0 = M_INT_2(clut, *(ushort*)&pgt3->uv0); cp->tpage_uv1 = M_INT_2(texture_pages[pgt3->texture_set], *(ushort *)&pgt3->uv1); cp->uv3_uv2 = *(ushort *)&pgt3->uv2; cp->originalindex = i; @@ -1092,12 +1095,14 @@ void buildNewCarFromModel(CAR_MODEL *car, MODEL *model, int first) { POLYGT4* pgt4 = (POLYGT4*)polyList; - clut = GetCarPalIndex(pgt4->texture_set); - civ_clut[clut][pgt4->texture_id][0] = texture_cluts[pgt4->texture_set][pgt4->texture_id]; + carid = GetCarPalIndex(pgt4->texture_set); + clut = (carid - 1) * 6 * 32 + pgt4->texture_id * 6; + + civ_clut[carid][pgt4->texture_id][0] = texture_cluts[pgt4->texture_set][pgt4->texture_id]; cp->vindices = M_INT_4R(pgt4->v0, pgt4->v1, pgt4->v2, 0); cp->nindices = M_INT_4R(pgt4->n0, pgt4->n1, pgt4->n2, 0); - cp->clut_uv0 = M_INT_2((clut * 384 + pgt4->texture_id * 12 - 384) >> 1, *(ushort*)&pgt4->uv0); + cp->clut_uv0 = M_INT_2(clut, *(ushort*)&pgt4->uv0); cp->tpage_uv1 = M_INT_2(texture_pages[pgt4->texture_set], *(ushort*)&pgt4->uv1); cp->uv3_uv2 = *(ushort*)&pgt4->uv2; cp->originalindex = i; @@ -1106,7 +1111,7 @@ void buildNewCarFromModel(CAR_MODEL *car, MODEL *model, int first) cp->vindices = M_INT_4R(pgt4->v0, pgt4->v2, pgt4->v3, 0); cp->nindices = M_INT_4R(pgt4->n0, pgt4->n2, pgt4->n3, 0); - cp->clut_uv0 = M_INT_2((clut * 384 + pgt4->texture_id * 12 - 384) >> 1, *(ushort*)&pgt4->uv0); + cp->clut_uv0 = M_INT_2(clut, *(ushort*)&pgt4->uv0); cp->tpage_uv1 = M_INT_2(texture_pages[pgt4->texture_set], *(ushort *)&pgt4->uv2); cp->uv3_uv2 = *(ushort *)&pgt4->uv3; cp->originalindex = i; @@ -1231,7 +1236,7 @@ void MangleWheelModels(void) src++; } - } while (++i < 3); + } // HACK: Show clean model only in Rio. //if (GameLevel == 3) @@ -1265,24 +1270,23 @@ void ProcessPalletLump(char *lump_ptr, int lump_size) texnum = buffPtr[1]; tpageindex = buffPtr[2]; clut_number = buffPtr[3]; + buffPtr += 4; if (clut_number == -1) { // store clut - LoadImage(&clutpos, (u_long*)(buffPtr + 4)); + LoadImage(&clutpos, (u_long*)buffPtr); + buffPtr += 8; clutValue = GetClut(clutpos.x, clutpos.y); - *clutTablePtr++ = clutValue; - IncrementClutNum(&clutpos); - buffPtr += 12; + *clutTablePtr++ = clutValue; } else { // use stored clut clutValue = clutTable[clut_number]; - buffPtr += 4; } civ_clut[GetCarPalIndex(tpageindex)][texnum][palette + 1] = clutValue; diff --git a/src_rebuild/Game/C/cosmetic.c b/src_rebuild/Game/C/cosmetic.c index 6e7f6399..be54e745 100644 --- a/src_rebuild/Game/C/cosmetic.c +++ b/src_rebuild/Game/C/cosmetic.c @@ -68,7 +68,9 @@ void ProcessCosmeticsLump(char *lump_ptr, int lump_size) car_cosmetics[i] = *(CAR_COSMETICS*)((u_char*)lump_ptr + offset); #ifndef PSX - LoadCustomCarCosmetics(&car_cosmetics[i], model); + extern int gContentOverride; + if(gContentOverride) + LoadCustomCarCosmetics(&car_cosmetics[i], model); #endif FixCarCos(&car_cosmetics[i], model); } diff --git a/src_rebuild/Game/C/denting.c b/src_rebuild/Game/C/denting.c index c2662940..e45229a8 100644 --- a/src_rebuild/Game/C/denting.c +++ b/src_rebuild/Game/C/denting.c @@ -28,7 +28,7 @@ char* DentingFiles[] = u_char gCarDamageZoneVerts[MAX_CAR_MODELS][NUM_DAMAGE_ZONES][MAX_DAMAGE_ZONE_VERTS]; u_char gHDCarDamageZonePolys[MAX_CAR_MODELS][NUM_DAMAGE_ZONES][MAX_DAMAGE_ZONE_POLYS]; -u_char gHDCarDamageLevels[MAX_CAR_MODELS][MAX_DAMAGE_LEVELS]; +u_char gHDCarDamageLevels[MAX_CAR_MODELS][MAX_DAMAGE_LEVELS]; // the damage level (texture) count for polygons // [D] [T] void InitialiseDenting(void) @@ -66,12 +66,11 @@ void DentCar(CAR_DATA *cp) // collect vertices from zones if (pCleanModel != NULL) { - VertNo = 0; - while (VertNo < pCleanModel->num_vertices) - tempDamage[VertNo++] = 0; + for (VertNo = 0; VertNo < pCleanModel->num_vertices; VertNo++) + tempDamage[VertNo] = 0; - Zone = 0; - do { + for (Zone = 0; Zone < NUM_DAMAGE_ZONES; Zone++) + { Damage = cp->ap.damage[Zone]; if (Damage > MaxDamage) @@ -79,21 +78,14 @@ void DentCar(CAR_DATA *cp) DamPtr = gCarDamageZoneVerts[cp->ap.model][Zone]; - VertNo = 0; - while (VertNo < MAX_DAMAGE_ZONE_VERTS && *DamPtr != 0xFF) + for (VertNo = 0; VertNo < MAX_DAMAGE_ZONE_VERTS && *DamPtr != 0xFF; VertNo++, DamPtr++) { if (tempDamage[*DamPtr] == 0) tempDamage[*DamPtr] += Damage; else tempDamage[*DamPtr] += Damage / 2; - - DamPtr++; - - VertNo++; } - - Zone++; - } while (Zone < NUM_DAMAGE_ZONES); + } } // update vertices positon @@ -102,17 +94,11 @@ void DentCar(CAR_DATA *cp) DamVertPtr = (SVECTOR *)gCarDamModelPtr[model]->vertices; CleanVertPtr = (SVECTOR *)gCarCleanModelPtr[model]->vertices; - VertNo = 0; - while (VertNo < pCleanModel->num_vertices) + for (VertNo = 0; VertNo < pCleanModel->num_vertices; VertNo++, DamVertPtr++, CleanVertPtr++) { gTempCarVertDump[cp->id][VertNo].vx = CleanVertPtr->vx + FIXEDH((DamVertPtr->vx - CleanVertPtr->vx) * tempDamage[VertNo] / 2); gTempCarVertDump[cp->id][VertNo].vy = CleanVertPtr->vy + FIXEDH((DamVertPtr->vy - CleanVertPtr->vy) * tempDamage[VertNo] / 2); gTempCarVertDump[cp->id][VertNo].vz = CleanVertPtr->vz + FIXEDH((DamVertPtr->vz - CleanVertPtr->vz) * tempDamage[VertNo] / 2); - - DamVertPtr++; - CleanVertPtr++; - - VertNo++; } } @@ -122,21 +108,22 @@ void DentCar(CAR_DATA *cp) dentptr = gTempHDCarUVDump[cp->id]; // reset UV coordinates - Poly = 0; - while (Poly < pCleanModel->num_polys) + + for (Poly = 0; Poly < pCleanModel->num_polys; Poly++) { dentptr->u3 = 0; - Poly++; dentptr++; } - Zone = 0; - do { + for(Zone = 0; Zone < NUM_DAMAGE_ZONES; Zone++) + { Damage = cp->ap.damage[Zone]; - Poly = 0; - while (Poly < MAX_DAMAGE_ZONE_POLYS && gHDCarDamageZonePolys[cp->ap.model][Zone][Poly] != 0xFF) + for (Poly = 0; Poly < MAX_DAMAGE_ZONE_POLYS; Poly++) { + if (gHDCarDamageZonePolys[cp->ap.model][Zone][Poly] == 0xFF) + break; + dentptr = gTempHDCarUVDump[cp->id] + gHDCarDamageZonePolys[cp->ap.model][Zone][Poly]; // add a damage level @@ -145,28 +132,17 @@ void DentCar(CAR_DATA *cp) // clamp level if (dentptr->u3 > 2) dentptr->u3 = 2; - - Poly++; } - - Zone++; - } while (Zone < NUM_DAMAGE_ZONES); - - Poly = 0; + } DamPtr = gHDCarDamageLevels[model]; dentptr = gTempHDCarUVDump[cp->id]; - while (Poly < pCleanModel->num_polys) + for (Poly = 0; Poly < pCleanModel->num_polys; Poly++, DamPtr++, dentptr++) { // calculate the UV offset with strange XORs if(dentptr->u3 > 0) dentptr->u3 = (*DamPtr ^ 1 ^ (*DamPtr ^ 1 | dentptr->u3)) * 64; - - dentptr++; - - DamPtr++; - Poly++; } } } @@ -194,11 +170,9 @@ void CreateDentableCar(CAR_DATA *cp) while (vcount-- != -1) *dst++ = *src++; - count = 0; - while (count < srcModel->num_polys) + for (count = 0; count < srcModel->num_polys; count++) { gTempHDCarUVDump[cp->id][count].u3 = 0; - count++; } } else @@ -209,11 +183,9 @@ void CreateDentableCar(CAR_DATA *cp) srcModel = gCarLowModelPtr[model]; if (srcModel != NULL) { - count = 0; - while (count < srcModel->num_polys) + for (count = 0; count < srcModel->num_polys; count++) { gTempLDCarUVDump[cp->id][count].u3 = 0; - count++; } } else @@ -223,11 +195,9 @@ void CreateDentableCar(CAR_DATA *cp) if (gDontResetCarDamage == 0) { - count = 0; - while (count < NUM_DAMAGE_ZONES) + for (count = 0; count < NUM_DAMAGE_ZONES; count++) { cp->ap.damage[count] = 0; - count++; } cp->totalDamage = 0; @@ -436,9 +406,7 @@ void ProcessDentLump(char *lump_ptr, int lump_size) int offset; u_char* mem; - i = 0; - - while (i < MAX_CAR_MODELS) + for (i = 0; i < MAX_CAR_MODELS; i++) { model = MissionHeader->residentModels[i]; @@ -457,11 +425,15 @@ void ProcessDentLump(char *lump_ptr, int lump_size) offset = *(int *)(lump_ptr + model * 4); mem = (u_char*)lump_ptr; #ifndef PSX - char* newDenting = LoadCarDentingFromFile(NULL, model); - if(newDenting) + extern int gContentOverride; + if (gContentOverride) { - mem = (u_char*)newDenting; - offset = 0; + char* newDenting = LoadCarDentingFromFile(NULL, model); + if (newDenting) + { + mem = (u_char*)newDenting; + offset = 0; + } } #endif @@ -473,8 +445,6 @@ void ProcessDentLump(char *lump_ptr, int lump_size) memcpy((u_char*)gHDCarDamageLevels[i], mem + offset, MAX_FILE_DAMAGE_LEVELS); } - - i++; } } diff --git a/src_rebuild/Game/C/leadai.c b/src_rebuild/Game/C/leadai.c index f122b69b..46af6781 100644 --- a/src_rebuild/Game/C/leadai.c +++ b/src_rebuild/Game/C/leadai.c @@ -2816,8 +2816,9 @@ u_int hypot(int x, int y) if (x < y) { + t = y; y = x; - x = y; + x = t; } if (x < 0x8000) diff --git a/src_rebuild/Game/C/models.c b/src_rebuild/Game/C/models.c index 71572e85..0b8bd543 100644 --- a/src_rebuild/Game/C/models.c +++ b/src_rebuild/Game/C/models.c @@ -292,7 +292,7 @@ char* LoadCarModelFromFile(char* dest, int modelNumber, int type) char* mem; char filename[64]; - sprintf(filename, "LEVELS\\%s\\CARMODEL_%d_%s.DMODEL", LevelNames[GameLevel], modelNumber, CarModelTypeNames[type-1]); + sprintf(filename, "LEVELS\\%s\\CARMODEL_%d_%s.MDL", LevelNames[GameLevel], modelNumber, CarModelTypeNames[type-1]); if(FileExists(filename)) { mem = (char*)(dest ? dest : (_other_buffer + modelNumber * 0x10000 + (type-1) * 0x4000)); @@ -314,10 +314,18 @@ MODEL* GetCarModel(char *src, char **dest, int KeepNormals, int modelNumber, int char* mem; #ifndef PSX - mem = LoadCarModelFromFile(NULL, modelNumber, type); + extern int gContentOverride; + if (gContentOverride) + { + mem = LoadCarModelFromFile(NULL, modelNumber, type); - if (!mem) // fallback to lump + if (!mem) // fallback to lump + mem = src; + } + else + { mem = src; + } #else mem = src; #endif diff --git a/src_rebuild/Game/C/motion_c.c b/src_rebuild/Game/C/motion_c.c index 0d201470..b8bca490 100644 --- a/src_rebuild/Game/C/motion_c.c +++ b/src_rebuild/Game/C/motion_c.c @@ -16,7 +16,7 @@ #include "cars.h" #include "convert.h" -#ifdef USE_PGXP +#if USE_PGXP #include #endif @@ -611,7 +611,7 @@ void DrawBodySprite(LPPEDESTRIAN pDrawingPed, int boneId, VERTTYPE v1[2], VERTTY prims->x3 = v2[0] - FIXEDH(cs) - dx1; prims->y3 = v2[1] - FIXEDH(sn) - dy1; -#ifdef USE_PGXP +#if USE_PGXP if (!bDoingShadow) // [A] Psy-X is currently incorrectly offsets the offscreen PGXP geometry. We don't need it anyway. { ushort pgxpIdx = PGXP_GetIndex(0) - 64; diff --git a/src_rebuild/Game/C/pause.c b/src_rebuild/Game/C/pause.c index 582d976a..e2d8d434 100644 --- a/src_rebuild/Game/C/pause.c +++ b/src_rebuild/Game/C/pause.c @@ -287,7 +287,6 @@ MENU_ITEM DebugOptionsItems[] = { { "Spawn position", PAUSE_TYPE_SUBMENU, 2, NULL, MENU_QUIT_NONE, &DebugSpawnPositionHeader }, #ifdef CUTSCENE_RECORDER - //{ gCutsceneRecorderPauseText, 5u, 2u, (pauseFunc)&NextCutsceneRecorderPlayer, MENU_QUIT_NONE, NULL }, { gCurrentChasePauseText, 5u, 2u, (pauseFunc)&CutRec_NextChase, MENU_QUIT_NONE, NULL }, #endif { "Display position", PAUSE_TYPE_FUNC, 2, SetDisplayPosition, MENU_QUIT_NONE, NULL}, diff --git a/src_rebuild/Game/C/pres.c b/src_rebuild/Game/C/pres.c index 634c88fa..7debaf8a 100644 --- a/src_rebuild/Game/C/pres.c +++ b/src_rebuild/Game/C/pres.c @@ -7,19 +7,24 @@ extern int gShowMap; #ifndef PSX -#define STB_TRUETYPE_IMPLEMENTATION -#include "../utils/stb_truetype.h" -#include "../utils/targa.h" - #include "PsyX/PsyX_render.h" +#include "../utils/targa.h" +#include "../utils/hqfont.h" + #define HIRES_FONTS -#define HIRES_FONT_SIZE_W 768 -#define HIRES_FONT_SIZE_H 512 + +struct FONT_QUAD +{ + float x0, y0, s0, t0; // top-left + float x1, y1, s1, t1; // bottom-right +}; TextureID gHiresFontTexture = 0; TextureID gHiresDigitsTexture = 0; -stbtt_packedchar gSTBCharData[224]; // ASCII 32..126 is 95 glyphs +OUT_FN2RANGE gHiresFontRanges[4]; +OUT_FN2INFO gHiresFontCharData[4][224]; +int gHiresFontRangeCount = 0; int gFontScale = 4096; int gLastFontScale = 4096; @@ -34,7 +39,7 @@ void InitHiresFonts() { int width, height, bpp; - sprintf(namebuffer, "%s%s", gDataFolder, "GFX\\HQ\\DIGITS.TGA"); + sprintf(namebuffer, "%s%s", gDataFolder, "GFX\\HQ\\digits.tga"); FS_FixPathSlashes(namebuffer); if (LoadTGAImage(namebuffer, &data, width, height, bpp)) @@ -44,50 +49,51 @@ void InitHiresFonts() gHiresDigitsTexture = GR_CreateRGBATexture(width, height, data); } free(data); + data = NULL; } } // init font2 if(!gHiresFontTexture) { + gHiresFontRangeCount = 0; + + int width, height, bpp; int x, y; int size; FILE* fp; - sprintf(namebuffer, "%s%s", gDataFolder, "GFX\\HQ\\ariblk.ttf"); + sprintf(namebuffer, "%s%s", gDataFolder, "GFX\\HQ\\font2.fn2"); + FS_FixPathSlashes(namebuffer); fp = fopen(namebuffer, "rb"); if (fp) { - // read whole file - fseek(fp, 0, SEEK_END); - size = ftell(fp); - fseek(fp, 0, SEEK_SET); - data = (u_char*)malloc(size); - fread(data, 1, size, fp); - fclose(fp); + int i; - // gen font - u_char* tmpBitmap = (u_char*)malloc(HIRES_FONT_SIZE_W * HIRES_FONT_SIZE_H); - u_int* bitmapRGBA = (u_int*)malloc(HIRES_FONT_SIZE_W * HIRES_FONT_SIZE_H * 4); + // read fn2 step by step + OUT_FN2HEADER fn2hdr; + fread(&fn2hdr, sizeof(fn2hdr), 1, fp); - stbtt_pack_context pc; - - stbtt_PackBegin(&pc, tmpBitmap, HIRES_FONT_SIZE_W, HIRES_FONT_SIZE_H, 0, 1, NULL); - stbtt_PackSetOversampling(&pc, 2, 2); - stbtt_PackFontRange(&pc, data, 0, 40.0f, 32, 224, gSTBCharData); - stbtt_PackEnd(&pc); - - for (x = 0; x < HIRES_FONT_SIZE_W; ++x) + gHiresFontRangeCount = fn2hdr.range_count; + for (i = 0; i < fn2hdr.range_count; ++i) { - for (y = 0; y < HIRES_FONT_SIZE_H; ++y) - { - bitmapRGBA[x + y * HIRES_FONT_SIZE_W] = tmpBitmap[x + y * HIRES_FONT_SIZE_W] << 24 | 0xffffff; - } + fread(&gHiresFontRanges[i], sizeof(gHiresFontRanges[i]), 1, fp); + fread(gHiresFontCharData[i], sizeof(OUT_FN2INFO), gHiresFontRanges[i].count, fp); } - gHiresFontTexture = GR_CreateRGBATexture(HIRES_FONT_SIZE_W, HIRES_FONT_SIZE_H, (u_char*)bitmapRGBA); - free(bitmapRGBA); - free(tmpBitmap); + fclose(fp); + } + + // load TGA file + sprintf(namebuffer, "%s%s", gDataFolder, "GFX\\HQ\\font2.tga"); + FS_FixPathSlashes(namebuffer); + + if (LoadTGAImage(namebuffer, &data, width, height, bpp)) + { + if (bpp == 32) + { + gHiresFontTexture = GR_CreateRGBATexture(HIRES_FONT_SIZE_W, HIRES_FONT_SIZE_H, data); + } free(data); } } @@ -134,15 +140,14 @@ void SetHiresDigitsTexture(int enabled) current->primptr += sizeof(DR_PSYX_TEX); } -void GetHiresBakedQuad(int char_index, float* xpos, float* ypos, stbtt_aligned_quad* q) +void GetHiresBakedQuad(int char_index, float* xpos, float* ypos, FONT_QUAD* q) { float ipw = 1.0f / (float)HIRES_FONT_SIZE_W; float iph = 1.0f / (float)HIRES_FONT_SIZE_H; - const stbtt_packedchar* b = gSTBCharData + char_index; + const OUT_FN2INFO* b = gHiresFontCharData[0] + char_index - gHiresFontRanges[0].start; float scaling = gFontScale / 4096.f; - - float scale = 0.45f * scaling; + float scale = 0.275f; float s_x = b->x1 - b->x0; float s_y = b->y1 - b->y0; @@ -183,8 +188,8 @@ int StrighWidthHires(char* string) float fx, fy; fx = 0.0f; fy = 0.0f; - stbtt_aligned_quad q; - GetHiresBakedQuad(chr - 32, &fx, &fy, &q); + FONT_QUAD q; + GetHiresBakedQuad(chr, &fx, &fy, &q); width += fx; } return width; @@ -232,8 +237,8 @@ int PrintStringHires(char* string, int x, int y) float fx, fy; fx = width; fy = y; - stbtt_aligned_quad q; - GetHiresBakedQuad(chr - 32, &fx, &fy, &q); + FONT_QUAD q; + GetHiresBakedQuad(chr, &fx, &fy, &q); fontFT4 = (POLY_FT4*)current->primptr; diff --git a/src_rebuild/Game/C/pres.h b/src_rebuild/Game/C/pres.h index 81489a7d..4840c254 100644 --- a/src_rebuild/Game/C/pres.h +++ b/src_rebuild/Game/C/pres.h @@ -3,12 +3,9 @@ struct OUT_FONTINFO { - u_char x; - u_char y; - char offx; - char offy; - u_char width; - u_char height; + u_char x, y; + char offx, offy; + u_char width, height; u_short pad; }; diff --git a/src_rebuild/Game/C/sky.c b/src_rebuild/Game/C/sky.c index 4c5df161..3c13dcd5 100644 --- a/src_rebuild/Game/C/sky.c +++ b/src_rebuild/Game/C/sky.c @@ -285,7 +285,7 @@ void LoadSky(void) } // [D] [T] -#ifdef USE_PGXP +#if USE_PGXP void DisplaySun(DVECTORF* pos, CVECTOR* col, int flare_col) #else void DisplaySun(DVECTOR* pos, CVECTOR* col, int flare_col) @@ -393,7 +393,7 @@ void DisplaySun(DVECTOR* pos, CVECTOR* col, int flare_col) } // [D] [T] -#ifdef USE_PGXP +#if USE_PGXP void DisplayMoon(DVECTORF* pos, CVECTOR* col, int flip) #else void DisplayMoon(DVECTOR* pos, CVECTOR* col, int flip) @@ -492,7 +492,7 @@ void DrawLensFlare(void) int haze_col; -#ifdef USE_PGXP +#if USE_PGXP DVECTORF sun_pers_conv_position; #else DVECTOR sun_pers_conv_position; @@ -649,7 +649,7 @@ void DrawLensFlare(void) } } -#ifdef USE_PGXP +#if USE_PGXP // remap PsyX_GetPSXWidescreenMappedViewport(&viewp); sun_pers_conv_position.vx = RemapVal(sun_pers_conv_position.vx, float(viewp.x), float(viewp.w), 0.0f, 320.0f); @@ -818,7 +818,7 @@ void calc_sky_brightness(RGB16* skycolor) } } -#ifdef USE_PGXP +#if USE_PGXP DVECTORF scratchPad_skyVertices[35]; // 1f800044 #else #define scratchPad_skyVertices ((DVECTOR*)getScratchAddr(0x11)) // 1f800044 @@ -834,7 +834,7 @@ void PlotSkyPoly(POLYFT4* polys, int skytexnum, unsigned char r, unsigned char g src = polys; poly = (POLY_FT4*)current->primptr; -#ifdef USE_PGXP +#if USE_PGXP DVECTORF* outpoints = scratchPad_skyVertices; #else DVECTOR* outpoints = scratchPad_skyVertices; @@ -867,7 +867,7 @@ void PlotSkyPoly(POLYFT4* polys, int skytexnum, unsigned char r, unsigned char g addPrim(current->ot + OTSIZE - 1, poly); -#if defined(USE_PGXP) && defined(USE_EXTENDED_PRIM_POINTERS) +#if USE_PGXP && USE_EXTENDED_PRIM_POINTERS poly->pgxp_index = outpoints[src->v0].pgxp_index; #endif @@ -880,7 +880,7 @@ void PlotHorizonMDL(MODEL* model, int horizontaboffset, RGB16* skycolor) { SVECTOR* verts; -#ifdef USE_PGXP +#if USE_PGXP DVECTORF* dv; #else DVECTOR* dv; @@ -896,7 +896,7 @@ void PlotHorizonMDL(MODEL* model, int horizontaboffset, RGB16* skycolor) dv = scratchPad_skyVertices; count = model->num_vertices; -#ifdef USE_PGXP +#if USE_PGXP PGXP_SetZOffsetScale(0.0f, 256.0f); #endif @@ -909,7 +909,7 @@ void PlotHorizonMDL(MODEL* model, int horizontaboffset, RGB16* skycolor) if(count == 15) gte_stszotz(&z); -#ifdef USE_PGXP +#if USE_PGXP // store PGXP index // HACK: -1 is needed here for some reason dv[0].pgxp_index = dv[1].pgxp_index = dv[2].pgxp_index = PGXP_GetIndex(0) - 1; @@ -919,7 +919,7 @@ void PlotHorizonMDL(MODEL* model, int horizontaboffset, RGB16* skycolor) count -= 3; } while (count); -#ifdef USE_PGXP +#if USE_PGXP PGXP_SetZOffsetScale(0.0f, 1.0f); #endif diff --git a/src_rebuild/Game/C/sound.c b/src_rebuild/Game/C/sound.c index e2af2c0d..5d6cdb08 100644 --- a/src_rebuild/Game/C/sound.c +++ b/src_rebuild/Game/C/sound.c @@ -473,41 +473,39 @@ int CompleteSoundSetup(int channel, int bank, int sample, int pitch, int proximi if (bpf == 0) { - channel = -1; + return -1; } - else - { - chan = &channels[channel]; + + chan = &channels[channel]; - if (gSoundMode == 1 && proximity != -1) - UpdateVolumeAttributesS(channel, proximity); - else - UpdateVolumeAttributesM(channel); + if (gSoundMode == 1 && proximity != -1) + UpdateVolumeAttributesS(channel, proximity); + else + UpdateVolumeAttributesM(channel); - stop_sound_handler = 1; + stop_sound_handler = 1; - chan->attr.mask = SPU_VOICE_VOLL | SPU_VOICE_VOLR | SPU_VOICE_VOLMODEL | SPU_VOICE_VOLMODER | SPU_VOICE_PITCH | SPU_VOICE_WDSA; - chan->attr.addr = samp->address; - chan->attr.pitch = MIN(rate / 44100, 16383); - chan->time = (samp->length / bpf) * 2 + 2; + chan->attr.mask = SPU_VOICE_VOLL | SPU_VOICE_VOLR | SPU_VOICE_VOLMODEL | SPU_VOICE_VOLMODER | SPU_VOICE_PITCH | SPU_VOICE_WDSA; + chan->attr.addr = samp->address; + chan->attr.pitch = MIN(rate / 44100, 16383); + chan->time = (samp->length / bpf) * 2 + 2; - chan->flags &= ~CHAN_LOOP; - chan->flags |= samp->loop ? CHAN_LOOP : 0; + chan->flags &= ~CHAN_LOOP; + chan->flags |= samp->loop ? CHAN_LOOP : 0; - chan->samplerate = samp->samplerate; + chan->samplerate = samp->samplerate; - if (sound_paused != 0) - { - chan->attr.volume.left = 0; - chan->attr.volume.right = 0; - } - - SpuSetVoiceAttr(&chan->attr); - SpuSetKey(1, chan->attr.voice); - - stop_sound_handler = 0; + if (sound_paused != 0) + { + chan->attr.volume.left = 0; + chan->attr.volume.right = 0; } + SpuSetVoiceAttr(&chan->attr); + SpuSetKey(1, chan->attr.voice); + + stop_sound_handler = 0; + return channel; } @@ -591,6 +589,9 @@ int Start3DTrackingSound(int channel, int bank, int sample, VECTOR *position, LO channel = CompleteSoundSetup(channel, bank, sample, 4096, 0); + if (channel < 0) + return -1; + ComputeDoppler(&channels[channel]); SetChannelPitch(channel, 4096); @@ -619,6 +620,9 @@ int Start3DSoundVolPitch(int channel, int bank, int sample, int x, int y, int z, channel = CompleteSoundSetup(channel, bank, sample, pitch, 0); + if (channel < 0) + return -1; + ComputeDoppler(&channels[channel]); SetChannelPitch(channel, pitch); diff --git a/src_rebuild/Game/C/spool.c b/src_rebuild/Game/C/spool.c index 7ee7e925..a527bbfc 100644 --- a/src_rebuild/Game/C/spool.c +++ b/src_rebuild/Game/C/spool.c @@ -715,6 +715,7 @@ void CheckValidSpoolData(void) if (models_ready) init_spooled_models(); +#ifdef PSX if (spoolactive && check_regions_present()) { stopgame(); @@ -727,6 +728,7 @@ void CheckValidSpoolData(void) startgame(); } +#endif // PSX } // [D] [T] @@ -759,14 +761,6 @@ void CheckLoadAreaData(int cellx, int cellz) spoolptr = (Spool *)(RegionSpoolInfo + spoolinfo_offsets[current_region]); -#ifndef PSX - // [A] this fixes spooling not activated bug (reversing bug?) - if (LoadedArea != spoolptr->super_region && spoolptr->super_region != 0xFF && old_region != -1) - { - LoadedArea = spoolptr->super_region; - } - else -#endif if (old_region == -1 && spoolptr->super_region != 0xFF) { // just load the area if no @@ -778,7 +772,7 @@ void CheckLoadAreaData(int cellx, int cellz) if (old_region == -1) LoadedArea = -1; - else if (/*spoolptr->super_region == 0xFF ||*/ nAreas == 0) + else if (spoolptr->super_region == 0xFF && nAreas == 0) return; #define BOUNDARY_MIN 15 diff --git a/src_rebuild/Game/C/system.c b/src_rebuild/Game/C/system.c index 3fb23ae4..aeb5c252 100644 --- a/src_rebuild/Game/C/system.c +++ b/src_rebuild/Game/C/system.c @@ -850,7 +850,7 @@ void InitaliseDrawEnv(DB* pBuff, int x, int y, int w, int h) pBuff[1].id = 1; pBuff[1].draw.dfe = 1; -#ifdef USE_PGXP +#if USE_PGXP if(NumPlayers == 2) { pBuff[0].draw.clip.x -= 256; diff --git a/src_rebuild/Game/C/tile.c b/src_rebuild/Game/C/tile.c index afab27c8..9dcfa703 100644 --- a/src_rebuild/Game/C/tile.c +++ b/src_rebuild/Game/C/tile.c @@ -38,7 +38,7 @@ void Tile1x1Lit(MODEL* model) else ofse = 133; -#ifdef USE_PGXP +#if USE_PGXP PGXP_SetZOffsetScale(0.0f, ofse > 200 ? 1.005f : 0.995f); #endif @@ -116,7 +116,7 @@ void Tile1x1Lit(MODEL* model) polys = (PL_POLYFT4*)((char*)polys + plotContext.polySizes[ptype]); } -#ifdef USE_PGXP +#if USE_PGXP PGXP_SetZOffsetScale(0.0f, 1.0f); #endif @@ -145,7 +145,7 @@ void Tile1x1(MODEL *model) else ofse = 133; -#ifdef USE_PGXP +#if USE_PGXP PGXP_SetZOffsetScale(0.0f, ofse > 200 ? 1.005f : 0.995f); #endif @@ -199,7 +199,7 @@ void Tile1x1(MODEL *model) polys = (PL_POLYFT4*)((char*)polys + plotContext.polySizes[ptype]); } -#ifdef USE_PGXP +#if USE_PGXP PGXP_SetZOffsetScale(0.0f, 1.0f); #endif @@ -664,7 +664,7 @@ void TileNxN(MODEL *model, int levels, int Dofse) ttype = 0; while (i--) { -#ifdef USE_PGXP +#if USE_PGXP switch (ttype) { case 0: @@ -688,7 +688,7 @@ void TileNxN(MODEL *model, int levels, int Dofse) polys += plotContext.polySizes[*polys]; } -#ifdef USE_PGXP +#if USE_PGXP PGXP_SetZOffsetScale(0.0f, 1.0f); #endif } diff --git a/src_rebuild/Game/Frontend/FEmain.c b/src_rebuild/Game/Frontend/FEmain.c index 526418d3..472e24aa 100644 --- a/src_rebuild/Game/Frontend/FEmain.c +++ b/src_rebuild/Game/Frontend/FEmain.c @@ -1,6 +1,7 @@ #include "driver2.h" #include "FEmain.h" +#include "FEtypes.h" #include "C/cd_icon.h" @@ -22,7 +23,259 @@ #include "C/spool.h" #include "C/state.h" -#include "FEtypes.h" +#ifndef PSX + +#include "PsyX/PsyX_render.h" +#include "../utils/targa.h" +#include "../utils/hqfont.h" + +#define HIRES_FONTS + +struct FEFONT_QUAD +{ + float x0, y0, s0, t0; // top-left + float x1, y1, s1, t1; // bottom-right +}; + +TextureID gHiresFEFontTexture = 0; +OUT_FN2RANGE gHiresFEFontRanges[4]; +OUT_FN2INFO gHiresFEFontCharData[4][224]; +int gHiresFEFontRangeCount = 0; + +void InitHiresFEFont() +{ + char namebuffer[64]; + u_char* data; + + // init font2 + if (!gHiresFEFontTexture) + { + gHiresFEFontRangeCount = 0; + + int width, height, bpp; + int x, y; + int size; + FILE* fp; + sprintf(namebuffer, "%s%s", gDataFolder, "GFX\\HQ\\fefont.fn2"); + FS_FixPathSlashes(namebuffer); + + fp = fopen(namebuffer, "rb"); + if (fp) + { + int i; + + // read fn2 step by step + OUT_FN2HEADER fn2hdr; + fread(&fn2hdr, sizeof(fn2hdr), 1, fp); + + gHiresFEFontRangeCount = fn2hdr.range_count; + for (i = 0; i < fn2hdr.range_count; ++i) + { + fread(&gHiresFEFontRanges[i], sizeof(gHiresFEFontRanges[i]), 1, fp); + fread(gHiresFEFontCharData[i], sizeof(OUT_FN2INFO), gHiresFEFontRanges[i].count, fp); + } + + fclose(fp); + } + + // load TGA file + sprintf(namebuffer, "%s%s", gDataFolder, "GFX\\HQ\\fefont.tga"); + FS_FixPathSlashes(namebuffer); + + if (LoadTGAImage(namebuffer, &data, width, height, bpp)) + { + if (bpp == 32) + { + gHiresFEFontTexture = GR_CreateRGBATexture(HIRES_FONT_SIZE_W, HIRES_FONT_SIZE_H, data); + } + free(data); + } + } +} + +void FEGetHiresBakedQuad(int char_index, float scale, float* xpos, float* ypos, FEFONT_QUAD* q) +{ + float ipw = 1.0f / (float)HIRES_FONT_SIZE_W; + float iph = 1.0f / (float)HIRES_FONT_SIZE_H; + + const OUT_FN2INFO* b = gHiresFEFontCharData[0] + char_index - gHiresFEFontRanges[0].start; + + float fscale = 0.5f * scale; + + float s_x = b->x1 - b->x0; + float s_y = b->y1 - b->y0; + + q->x0 = *xpos + b->xoff * fscale; + q->y0 = *ypos + b->yoff * fscale; + q->x1 = (b->xoff2 - b->xoff) * fscale; + q->y1 = (b->yoff2 - b->yoff) * fscale; + + q->s0 = b->x0 * 255.0f * ipw; + q->t0 = b->y0 * 255.0f * iph; + q->s1 = s_x * 255.0f * ipw; + q->t1 = s_y * 255.0f * iph; + + q->y0 += 32.0f; + + *xpos += b->xadvance * fscale; +} + +void SetHiresFEFontTexture(int enabled) +{ + if (gHiresFEFontTexture == 0) + { + return; + } + + DR_PSYX_TEX* tex = (DR_PSYX_TEX*)current->primptr; + if (enabled) + SetPsyXTexture(tex, gHiresFEFontTexture, 255, 255); + else + SetPsyXTexture(tex, 0, 0, 0); + + addPrim(current->ot + 1, tex); + current->primptr += sizeof(DR_PSYX_TEX); +} + +int FEStringWidthHires(char* string) +{ + char* pString = string; + u_char c = 0; + + int w = 0; + + while ((c = *pString++) != 0) + { + float fx, fy; + fx = 0; + fy = 0; + FEFONT_QUAD q; + FEGetHiresBakedQuad(c, 1.0f, &fx, &fy, &q); + + w += fx; + } + + return w; +} + +int FEPrintStringSizedHires(char* string, int x, int y, int scale, int transparent, int r, int g, int b) +{ + if (current == NULL || string == NULL) + return -1; + + POLY_FT4* shadow; + POLY_FT4* font; + u_char let; + int w; + int h; + + SetHiresFEFontTexture(0); + font = (POLY_FT4*)current->primptr; + + while ((let = *string++) != 0) + { + if (let == '\n') + continue; + + float fx, fy; + fx = x; + fy = y; + FEFONT_QUAD q; + FEGetHiresBakedQuad(let, scale / 4096.0f, &fx, &fy, &q); + + setPolyFT4(font); + setSemiTrans(font, 1); + + setRGB0(font, r, g, b); + setUVWH(font, q.s0, q.t0, q.s1, q.t1); + setXYWH(font, q.x0, q.y0, q.x1, q.y1); + + font->clut = 0; + font->tpage = 0; + + addPrim(current->ot + 1, font); + shadow = font + 1; + + // add shadow poly + memcpy(shadow, font, sizeof(POLY_FT4)); + setRGB0(shadow, 10, 10, 10); + setXYWH(shadow, q.x0 + 1.0f, q.y0 + 1.0f, q.x1, q.y1); + + addPrim(current->ot + 1, shadow); + font += 2; + + // make room for next character + x += fx - x; + } + + // set tail + current->primptr = (char*)font; + SetHiresFEFontTexture(1); + + return x; +} + +int FEPrintStringHires(char* string, float x, float y, int justification, int r, int g, int b) +{ + POLY_FT4* shadow; + POLY_FT4* font; + u_char let; + + if (justification & 4) + { + x -= FEStringWidthHires(string); + } + + SetHiresFEFontTexture(0); + font = (POLY_FT4*)current->primptr; + + int counter = 0; + + while ((let = *string++) != 0) + { + float fx, fy; + fx = x; + fy = y; + FEFONT_QUAD q; + FEGetHiresBakedQuad(let, 1.0f, &fx, &fy, &q); + + setPolyFT4(font); + setSemiTrans(font, 1); + + setRGB0(font, r, g, b); + setUVWH(font, q.s0, q.t0, q.s1, q.t1); + setXYWH(font, q.x0, q.y0, q.x1, q.y1); + + font->clut = 0; + font->tpage = 0; + + addPrim(current->ot + 1, font); + shadow = font + 1; + + // add shadow poly + memcpy(shadow, font, sizeof(POLY_FT4)); + setRGB0(shadow, 10, 10, 10); + setXYWH(shadow, q.x0 + 1.0f, q.y0 + 1.0f, q.x1, q.y1); + + addPrim(current->ot + 1, shadow); + font += 2; + + // add space for next character + x += fx - x; + + if (++counter >= 32) + break; + } + + // set tail + current->primptr = (char *)font; + + SetHiresFEFontTexture(1); + + return x; +} + +#endif // PSX #define FE_OTSIZE 16 @@ -1005,7 +1258,7 @@ void DisplayOnScreenText(void) text = "Incompatible controller in Port 1"; } - FEPrintStringSized(text, 40, 400, 0xc00, transparent, 64, 64, 64); + FEPrintStringSized(text, 40, 400, 3072, transparent, 64, 64, 64); } else { @@ -1019,14 +1272,14 @@ void DisplayOnScreenText(void) strcat(ScreenTitle, ScreenNames[i]); } - FEPrintStringSized(ScreenTitle, 40, 400, 0xc00, 1, 64, 64, 64); + FEPrintStringSized(ScreenTitle, 40, 400, 3072, 1, 64, 64, 64); } if (iScreenSelect == SCREEN_CUTSCENE) { text = GET_MISSION_TXT(CutSceneNames[cutSelection + CutAmountsTotal[currCity]]); - FEPrintStringSized(text, 100, 226, 0xc00, 1, 64, 64, 64); + FEPrintStringSized(text, 100, 226, 3072, 1, 64, 64, 64); } if (iScreenSelect == SCREEN_TIMEOFDAY) @@ -2170,6 +2423,9 @@ void SetFEDrawMode(void) void InitFrontend(void) { InitCdIcon(); +#ifdef HIRES_FONTS + InitHiresFEFont(); +#endif ResetGraph(1); SetDispMask(0); @@ -2322,9 +2578,15 @@ int FEStringWidth(char* string) { char* pString = string; u_char c = 0; - int w = 0; +#ifdef HIRES_FONTS + if (gHiresFEFontTexture) + { + return FEStringWidthHires(string); + } +#endif + while ((c = *pString++) != 0) { if (c == ' ') @@ -2342,30 +2604,24 @@ int FEPrintString(char *string, int x, int y, int justification, int r, int g, i if (current == NULL || string == NULL) return -1; +#ifdef HIRES_FONTS + if (gHiresFEFontTexture) + { + return FEPrintStringHires(string, x, y, justification, r, g, b); + } +#endif + FE_CHARDATA *pFontInfo; SPRT *font; u_char let; - font = (SPRT *)current->primptr; - if (justification & 4) { - char *pString = string; - u_char c = 0; - - int w = 0; - - while ((c = *pString++) != 0) - { - if (c == ' ') - w += 4; - else - w += feFont.CharInfo[c].w; - } - - x -= w; + x -= FEStringWidth(string); } + font = (SPRT*)current->primptr; + int counter = 0; while ((let = *string++) != 0) @@ -2423,6 +2679,13 @@ int FEPrintStringSized(char *string, int x, int y, int scale, int transparent, i if (current == NULL || string == NULL) return -1; +#ifdef HIRES_FONTS + if (gHiresFEFontTexture) + { + return FEPrintStringSizedHires(string, x, y, scale, transparent, r, g, b); + } +#endif + POLY_FT4 *font; FE_CHARDATA *pFontInfo; u_char let; @@ -2491,10 +2754,10 @@ int CentreScreen(int bSetup) char text[32]; sprintf(text, "X1: %d, Y1: %d", current->disp.screen.x, current->disp.screen.y); - FEPrintStringSized(text, 25, 50, 0xC00, 0, 128, 0, 0); + FEPrintStringSized(text, 25, 50, 3072, 0, 128, 0, 0); sprintf(text, "X2: %d, Y2: %d", last->disp.screen.x, last->disp.screen.y); - FEPrintStringSized(text, 25, 75, 0xC00, 0, 128, 0, 0); + FEPrintStringSized(text, 25, 75, 3072, 0, 128, 0, 0); #endif if (feNewPad & MPAD_CROSS) diff --git a/src_rebuild/Game/engine/mdl.h b/src_rebuild/Game/engine/mdl.h index 9ee02a21..8d552de8 100644 --- a/src_rebuild/Game/engine/mdl.h +++ b/src_rebuild/Game/engine/mdl.h @@ -160,26 +160,6 @@ struct MODEL int normals; int point_normals; int collision_block; - - SVECTOR* pVertex(int i) const - { - return (SVECTOR *)(((u_char *)this) + vertices) + i; - } - - SVECTOR* pNormal(int i) const - { - return (SVECTOR *)(((u_char *)this) + point_normals) + i; - } - - COLLISION_PACKET* pCollisionPacket(int i) const - { - return (COLLISION_PACKET *)(((u_char *)this) + collision_block) + i; - } - - char* pPolyAt(int ofs) const - { - return (char *)(((u_char *)this) + poly_block + ofs); - } }; diff --git a/src_rebuild/Game/version.h b/src_rebuild/Game/version.h index 49edbe37..9087f4a4 100644 --- a/src_rebuild/Game/version.h +++ b/src_rebuild/Game/version.h @@ -1,9 +1,9 @@ #ifndef GAME_VERSION_N -#define GAME_VERSION_N "7.6" +#define GAME_VERSION_N "7.8" #endif #ifndef GAME_VERSION_RES -#define GAME_VERSION_RES 7,6 +#define GAME_VERSION_RES 7,8 #endif #define GAME_TITLE "REDRIVER2" diff --git a/src_rebuild/premake5.lua b/src_rebuild/premake5.lua index ab56f11b..1b4a17ce 100644 --- a/src_rebuild/premake5.lua +++ b/src_rebuild/premake5.lua @@ -3,6 +3,8 @@ require "premake_modules/usage" require "premake_modules/emscripten" +IS_ANDROID = (_ACTION == "androidndk") + ------------------------------------------ newoption { @@ -58,6 +60,7 @@ workspace "REDRIVER2" "-Wno-parentheses", "-Wno-format", } + linkoptions { "-s TOTAL_MEMORY=1073741824", "-s USE_SDL=2", @@ -80,6 +83,51 @@ workspace "REDRIVER2" "{COPY} " .. WEBSHELL_PATH .. "/lsfs.js %{cfg.buildtarget.directory}" } + elseif IS_ANDROID then + system "android" + shortcommands "On" + + platforms { + "android-arm", "android-arm64" + } + + disablewarnings { + "c++11-narrowing", + "constant-conversion", + "writable-strings", + "unused-value", + "switch", + "shift-op-parentheses", + "parentheses", + "format", + } + + buildoptions { + "-fpermissive", + "-fexceptions", + "-pthread", + } + + linkoptions { + "--no-undefined", + "-fexceptions", + "-pthread", + + "-mfloat-abi=softfp", -- force NEON to be used + "-mfpu=neon" + } + + filter "platforms:*-x86" + architecture "x86" + + filter "platforms:*-x86_64" + architecture "x86_64" + + filter "platforms:*-arm" + architecture "arm" + + filter "platforms:*-arm64" + architecture "arm64" else platforms { "x86" } --, "x86_64" } end @@ -137,6 +185,9 @@ end -- Psy-Cross layer include "premake5_psycross.lua" +-- font tool +include "premake5_font_tool.lua" + -- game iteslf project "REDRIVER2" kind "WindowedApp" @@ -175,7 +226,6 @@ project "REDRIVER2" "utils/**.cpp", "utils/**.c", "redriver2_psxpc.cpp", - "DebugOverlay.cpp", } filter "platforms:emscripten" diff --git a/src_rebuild/premake5_font_tool.lua b/src_rebuild/premake5_font_tool.lua new file mode 100644 index 00000000..823c24df --- /dev/null +++ b/src_rebuild/premake5_font_tool.lua @@ -0,0 +1,33 @@ +-- Font generator tool +project "FontTool" + kind "ConsoleApp" + language "C++" + targetdir "bin/%{cfg.buildcfg}" + + files { + "tools/font_tool/**.h", + "tools/font_tool/**.H", + "tools/font_tool/**.c", + "tools/font_tool/**.C", + "tools/font_tool/**.cpp", + "tools/font_tool/**.CPP", + "utils/stb_truetype.*", + "utils/targa.*", + } + + defines { } + + includedirs { + "utils", + "PsyCross/include/psx" + } + + filter "system:Windows" + defines { "_WINDOWS" } + + filter "configurations:Release" + optimize "Speed" + + filter "configurations:Release_dev" + optimize "Speed" + diff --git a/src_rebuild/premake_modules/androidndk/LICENSE.txt b/src_rebuild/premake_modules/androidndk/LICENSE.txt new file mode 100644 index 00000000..b83a9213 --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2021 Vitaliy Triang3l Kuzmin. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of premake-androidndk nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src_rebuild/premake_modules/androidndk/README.md b/src_rebuild/premake_modules/androidndk/README.md new file mode 100644 index 00000000..4e018302 --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/README.md @@ -0,0 +1,541 @@ +# premake-androidndk + +A module for generation of ndk-build Android.mk files for [Premake 5](https://premake.github.io/). + +* Providing the same level of **multi-ABI building capabilities** as hand-written Android.mk files, allowing for building for multiple ABIs from one ndk-build invocation and supporting ABI filtering for most of the project settings. +* Supporting all languages accepted by ndk-build — C, C++, the GNU Assembler, Yasm, RenderScript, as well as prebuilt libraries and external Android.mk files. +* Focused on supporting as many Premake project settings as possible, with attention to coverage of edge cases such as allowed characters. +* Preferring exposing Android.mk settings through existing Premake project settings over adding new ones where semantically appropriate to allow porting of projects from other targets with minimal changes. + +Available under the [Unlicense](UNLICENSE.txt), or, similar to Premake, the [BSD 3-Clause “New” or “Revised” License](LICENSE.txt). + +## Usage + +### Generating Android.mk files + +Clone or download the module, and `require("path/to/premake-androidndk/androidndk")` in your workspace's Premake script. Use the `androidndk` action to perform generation. + +All projects — which represent ndk-build modules — in the application must be located in a single workspace, as a workspace corresponds to an Application.mk file and the root Android.mk. + +Add Android platform definitions to the `platforms` of your workspace or the projects in it. This is especially important in two cases — if your workspace targets other operating systems alongside Android, and if you want to specify ABI-specific settings. Using `configurations` for this purpose also works, but generally it's recommended to use the configuration axis for build flavors such as debug/release, while doing all platform and architecture filtering via the platform axis — and due to the nature of specifying platforms and configurations to be built by ndk-build for Android.mk files generated by this module, mixing the two may result in complicated setup and unnecessary duplication of arguments you'll need to pass to ndk-build. + +**Specify the `system` as `"android"`** in your Android platforms and configurations. While this module doesn't check the value of `system` anywhere internally, if it's not specified, Premake will assume that the host OS is the target, which may be undesirable — for instance, build libraries may not have the `"lib"` prefix if the `system` is selected as `"windows"`. + +If you need to specify settings that apply only to certain ABIs, you can specify separate `platforms` (or `configurations`) with different `architecture` values. The supported architectures are `"ARM"` (corresponds to the `armeabi-v7a` ABI), `"ARM64"` (`arm64-v8a` ABI), `"x86"` (`x86` ABI) and `"x86_64"` (`x86_64` ABI). + +For example: + +```lua +configurations({"Debug", "Release") +platforms({"Windows-x86_64", "Android-ARM64", "Android-x86_64"}) +filter({"platforms:Windows-*"}) + system("windows") + systemversion("10.0") +filter({"platforms:Android-*"}) + system("android") + systemversion("24") + cppstl("c++") +filter({"platforms:*-x86_64"}) + architecture("x86_64") +filter({"platforms:*-ARM64"}) + architecture("ARM64") +filter({}) +``` + +It's also possible to specify both architecture-specific and architecture-agnostic (with a nil or `"universal"` architecture) platforms in the same project. In this case, the architecture specialization will be chosen when ndk-build is building for an ABI for which one available, and the architecture-independent settings will be used as a fallback if one is not: + +```lua +platforms({"AndroidOther", "AndroidARMv7", "AndroidARMv8"}) +filter({"platforms:AndroidARMv7"}) + architecture("ARM") +filter({"platforms:AndroidARMv8"}) + architecture("ARM64") +filter({"architecture:ARM or ARM64"}) + files({"neonmath.cpp"}) +filter({}) +-- `armeabi-v7a` ABI will use the AndroidARMv7 platform with neonmath.cpp. +-- `arm64-v8a` ABI will use the AndroidARMv8 platform with neonmath.cpp. +-- `x86` and `x86_64` ABIs will use the AndroidOther platform without neonmath.cpp. +``` + +Note that deprecated ABIs such as `armeabi`, `armeabi-v7a-hard`, `mips`, `mips64` are not supported. `armeabi-v7a` builds by default will have NEON enabled and will use the Thumb instruction set for release configurations, this can be changed via settings listed in the section below. + +It is important to consider that **Premake settings such as `architecture` are assigned to configuration–platform pairs**. So, if you want to use `filter({"architecture:"})`, you **must create a platform or a configuration for that architecture**, otherwise it will not work. + +To exclude a project from building for certain ABIs, you can set its `kind` to `None` with the required filter. + +Most settings allow GNU make variable expansion and function call passthrough, so you can use environment variables, or variables and functions provided by ndk-build like `$(TARGET_ARCH_ABI)`, in your projects, and they will be expanded at build time. You need to be careful not to reference variables or functions that may result in disallowed characters in the specific context, such as whitespaces in settings that end up being written to a list variable, however — it's not possible for the module to validate the value in this case. To use the dollar sign as a character, specify `$$` instead. Paths starting with a dollar sign (even if it's `$$` denoting an escaped raw `$` character) are treated as absolute by Premake, allowing for the usage of paths relative to directories such as `$(NDK_ROOT)`. If you want to override this behavior, explicitly prefix the path with `./`. + +#### Usage of prebuilt libraries and external Android.mk files + +Normally, this module provides functionality for building of projects from source code. + +However, it also exposes the prebuilt library functionality of ndk-build. This is the recommended way of including non-system library binaries in the workspace and linking projects against them, as it lets ndk-build properly ensure that the library is copied into the destination directory properly and that it's loaded from the correct path. ndk-build will generate warnings if the project is linked against non-system libraries via system `links` or via `linkoptions`. + +In addition, it's also possible to create a project that will use an external Android.mk file instead of its own settings (certain settings still must be correctly specified for linkage purposes, however). + +To create a project using a prebuilt library or an external Android.mk file, specify **just one `.a`, `.so` or `.mk` file** and **no other source files** in the `files` setting for the desired configurations/platforms. Also, you **must specify the correct `kind`** of the project — `"SharedLib"` for `.so`, `"StaticLib"` for `.a`, or the kind that matches the actual build script included in the external Android.mk. + +Projects using an external Android.mk files have special rules regarding their usage, specifically: + +- The external Android.mk must contain settings for only at most one project (module). +- The `kind` of the project must match the build script used in the external Android.mk — `"SharedLib"` for `BUILD_SHARED_LIBRARY` or `PREBUILT_SHARED_LIBRARY`, `"StaticLib"` for `BUILD_STATIC_LIBRARY` or `PREBUILT_STATIC_LIBRARY`, `"ConsoleApp"` for `BUILD_EXECUTABLE`. **Do not use the `"Makefile"` kind** — it has a completely different purpose, and is not supported by this module. The module must know the actual `kind` for linkage. +- The name of the project must be the same as `LOCAL_MODULE` in the external Android.mk. + +The following settings have effect when used in a prebuilt library project or an external Android.mk project: + +- `configurations` +- `flags` + - `"ExcludeFromBuild"` + - `"LinkTimeOptimization"` (for static libraries, if any object files in it are built with `-flto`) +- `linkoptions` (the only supported options are `-u` or `-Wl,--undefined` exported from static libraries) +- `links` (must match `LOCAL_SHARED_LIBRARIES` and `LOCAL_STATIC_LIBRARIES`/`LOCAL_WHOLE_STATIC_LIBRARIES` for external Android.mk projects referencing other Premake projects, external or not; also, for static libraries, must include the system libraries from `LOCAL_EXPORT_LDLIBS`) +- `location` (or `basedir` if not provided) +- `kind` +- `platforms` +- `project` (must match `LOCAL_MODULE` in the external Android.mk) +- `wholelib` + +For example, to add the Android Native App Glue to your workspace, you can create a project with the following settings: + +```lua +project("android_native_app_glue") + kind("StaticLib") + files({"$(NDK_ROOT)/sources/android/native_app_glue/Android.mk"}) + links({"log", "android"}) + linkoptions({"-u ANativeActivity_onCreate"}) +``` + +#### RenderScript library linkage + +To link projects with RenderScript sources, you may need to add some of these prebuilt library projects to your workspace (most importantly `"RScpp_static"`) and to specify them in `links` of the projects that use RenderScript: + +```lua +project("RSSupport") + kind("SharedLib") + files({"$(RENDERSCRIPT_TOOLCHAIN_PREBUILT_ROOT)/platform/$(TARGET_ARCH)/libRSSupport.so"}) +project("RSSupportIO") + kind("SharedLib") + files({"$(RENDERSCRIPT_TOOLCHAIN_PREBUILT_ROOT)/platform/$(TARGET_ARCH)/libRSSupportIO.so"}) +project("blasV8") + kind("SharedLib") + files({"$(RENDERSCRIPT_TOOLCHAIN_PREBUILT_ROOT)/platform/$(TARGET_ARCH)/libblasV8.so"}) +project("RScpp_static") + kind("StaticLib") + files({"$(RENDERSCRIPT_TOOLCHAIN_PREBUILT_ROOT)/platform/$(TARGET_ARCH)/libRScpp_static.a"}) +``` + +### Invoking ndk-build + +#### Setting up applicaton settings + +The basic setup is different depending on whether you're using the `externalNativeBuild` functionality of Gradle to build the application. + +The module generates an Application.mk file containing basic settings for the entire application, with some support for Premake configuration and platform filtering that may be useful, for instance, for choosing `APP_DEBUG` and `APP_OPTIM` based on the selected configuration. However, as ABI filtering is not available in Application.mk, it will be more coarse than for settings that go to Android.mk — it is assumed that all Application.mk-level settings should not depend on the target ABI, you can see the exact list of the settings that go to Application.mk in the list of supported project settings. + +If you're launching ndk-build as a custom task, or not using Gradle at all, you need to specify the path to the Application.mk in the ndk-build command arguments, as `NDK_APPLICATION_MK:=path/to/WORKSPACE_NAME.Application.mk`, where `WORKSPACE_NAME` is the name of the Premake workspace for the application. The Application.mk file is written to the `location` directory of the workspace. + +**Gradle's `externalNativeBuild`, however, causes most Application.mk settings to be ignored and overridden by the settings specified in the Gradle script itself.** Because of this, using the Application.mk file generated by this module may not be absolutely necessary, but for correctness, you should still set the `NDK_APPLICATION_MK` variable in ndk-build arguments, otherwise it will try to use the Application.mk from the default path — it's better to express the intentions explicitly than to rely on the file not currently existing. However, you'll need to specify the correct settings in the Gradle script manually anyway. Because Gradle scripts contain settings not only for NDK, but also for all aspects of the application, and since the layout of configurations — build types and product flavors — may vary greatly between applications, Gradle setup is out of the scope of this module. + +Here is an example of configuring the application settings relevant to the usage of files generated by this module: + +```groovy +android { + // The Android NDK version to build the application with. + // If omitted, the latest installed version will be used. + ndkVersion '23.0.7599858' + + // Per-configuration Gradle settings. + // May be specified for defaultConfig, as well as for buildTypes and productFlavors. + defaultConfig { + // Minimum Android SDK version supported by the application. + // Corresponds to APP_PLATFORM := android- in Application.mk (Premake `systemversion`). + minSdkVersion 14 + + externalNativeBuild { + ndkBuild { + // See com.android.build.api.dsl.ExternalNativeNdkBuildOptions documentation. + + // Application.mk path - specifying the correct one is recommended. + arguments 'NDK_APPLICATION_MK:=path/to/WORKSPACE_NAME.Application.mk', + // Premake platforms to build for - see the section about configuration and platform selection below. + 'PREMAKE_ANDROIDNDK_PLATFORMS:=Android-ARM', + 'PREMAKE_ANDROIDNDK_PLATFORMS+=Android-ARM64' + } + } + + ndk { + // See com.android.build.api.dsl.Ndk documentation for more useful options such as `jobs`. + + // The list of the ABIs to build this application for. + // Corresponds to APP_ABI in Application.mk. + // Should match the actual list of ABIs supported by at least some of the projects in the workspace. + // If omitted, ndk-build will try to build for all ABIs supported by the used NDK version. + // In this case, you should provide an architecture-agnostic platform/configuration for the projects. + // It may be fine not to specify this if you target only a subset of APIs, but ndk-build will be emitting warnings. + abiFilters 'armeabi-v7a', 'arm64-v8a' + + // The C++ STL to use. + // Corresponds to APP_STL in Application.mk. + // If omitted, APP_STL (Premake `cppstl` and `staticruntime`) from the Application.mk will be used. + // If that's not specified too, the default STL for the current API version will be selected. + stl 'c++_static' + } + } + + buildTypes { + release { + debuggable false + + externalNativeBuild { + ndkBuild { + // Premake configurations to build for - see the section about configuration and platform selection below. + arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Release' + } + } + } + + debug { + debuggable true + + externalNativeBuild { + ndkBuild { + // Premake configurations to build for - see the section about configuration and platform selection below. + arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Debug' + } + } + } + } + + externalNativeBuild { + ndkBuild { + // See com.android.build.api.dsl.NdkBuild documentation. + // This is required to initiate native code building at all. + + // The Android.mk file to use - the generated workspace Android.mk in the `location` of the workspace. + // Corresponds to APP_BUILD_SCRIPT in Application.mk. + path file('path/to/WORKSPACE_NAME.wks.Android.mk') + } + } +} +``` + +#### Specifying Premake configurations and platforms to build + +Because a single ndk-build invocation builds for multiple ABIs, the usage of the generated files is slightly unusual compared to most build systems targeted by Premake where only a single configuration and platform pair is selected for building. + +Instead of letting you select just one configuration and platform, this module allows you to execute ndk-build for **multiple platforms and configurations**. + +For this purpose, the module provides two variables that you need to set in the arguments of the ndk-build invocation (`externalNativeBuild.ndkBuild.arguments` for a Gradle build configuration): + +* `PREMAKE_ANDROIDNDK_CONFIGURATIONS` — Premake configurations to build, **at least one must be specified**. +* `PREMAKE_ANDROIDNDK_PLATFORMS` — Premake platforms to build for. Optional, if none are specified, will be building for all platforms specified in any project. + +To set a variable to just one value, you can use the `VARIABLE:=value` syntax of the ndk-build argument. For multiple values, you need to provide each in a separate argument as `VARIABLE+=value` (preferably the first with `:=` still to ensure it's overwritten if there's an environment variable with the same name). The Gradle example above shows how you can enable building for multiple platforms representing different ABIs, and for a configuration depending on the Gradle build type. + +This, however, does not mean that you can create debug and release builds at once — rather, this architecture is designed specifically to provide a way for building for multiple ABIs as usual while being able to use an `architecture` filter in Premake. Because of this, **you must ensure** that **for every ABI, only at most one configuration–platform pair will be selected** for every project. An ambiguous selection of platforms and configurations will result in an undefined behavior. + +Platform-agnostic projects — those that have no `platforms` inherited from the workspace or defined — will be built regardless of the value of `PREMAKE_ANDROIDNDK_PLATFORMS` (only configuration selection will be used for them). + +## Supported settings + +Please carefully verify that the settings you're using in your projects are handled in a way that's supported by this module and by ndk-build itself. Most importantly, please **check the settings that are listed as per-file or per-extension here**, especially if you have source files of different languages in your projects. + +### Per-file or per-configuration/per-platform for a project, ABI-filtered + +There is a small number of settings for which it's possible to specify the value for individual source files using the `files` filter. + +- **`armisa`** (new) = `"A32"` / `"T32"` (default) + - ARMv7 instruction set to use. By default, Thumb (`"T32"`) will be used for release builds. Debug builds, however, always use A32. + - At the project configuration scope, it controls `LOCAL_ARM_MODE`. + - At the file scope, it can be used to build the specified files for A32 in a Thumb project, **but not vice versa**, by adding the `.arm` suffix to the file in `LOCAL_SRC_FILES`. Alternatively, you can use the `.arm` suffix (if needed, as a part of `.arm.neon`, but not `.neon.arm`) directly in the `files` setting, which will take precedence. +- **`flags`** + - `ExcludeFromBuild` + - Also supported for excluding entire projects on specified configurations/platforms. +- **`vectorextensions`** (for the `"ARM"` architecture) = `"ARMv7"` (new) / `"NEON"` (default, new) + - Whether to allow the compiler to generate instructions from the NEON SIMD instruction set as part of its optimizations. Specifying `"ARMv7"` (analogous to `"IA32"` on x86) disables NEON, specifying `"NEON"` enables it. By default, for consistency with NDK versions starting with r21, NEON will be enabled, even if the project doesn't explicitly specify `vectorextensions` (so on pre-r21, NEON will be used for Premake projects too). + - At the project configuration scope, it controls `LOCAL_ARM_NEON`. + - At the file scope, it can be used to build the specified files with NEON code generation in a project with NEON disabled, **but not vice versa**, by adding the `.neon` suffix to the file in `LOCAL_SRC_FILES`. Alternatively, you can use the `.neon` suffix (if needed, as a part of `.arm.neon`, but not `.neon.arm`) directly in the `files` setting, which will take precedence. + - For the allowed values on other architectures, see the per-configuration/per-platform settings section. + +### Per-language or per-configuration/per-platform for a project, ABI-filtered + +While ndk-build doesn't provide a way to specify compiler flags and most other settings for individual source files, it supports multiple source languages with different compilers, which accept different build options. Premake, however, exposes most build settings under language-agnostic names. + +To specify which compiler should receive the needed value, you can use file extension filters. + +Because the module supports `.arm`, `.neon` and `.arm.neon` suffixes, as well as many C++ extensions, it's not recommended to list file extensions manually. Instead, built-in filter constants provided by the module should be preferred, as they include the ARM suffixes and all the supported extensions. + +For including a specific language, the following variables are available (note that they must be used directly, and if needed, via `..` concatenation — **`%{}` string interpolation will not locate them**): + +- `premake.modules.androidndk.filefilters.as` for the GNU Assembler (GAS) +- `premake.modules.androidndk.filefilters.asm` for Yasm +- `premake.modules.androidndk.filefilters.c` for C +- `premake.modules.androidndk.filefilters.cpp` for C++ +- `premake.modules.androidndk.filefilters.rs` for RenderScript + +Each of those filters is a single string which already has the `"files:"` prefix (for instance, `premake.modules.androidndk.filefilters.c` is `"files:**.c or files:**.c.arm or files:**.c.neon or files:**.c.arm.neon"`). + +To exclude a language from a file filter, `filefilters` also includes filters prefixed with `not`, such as `premake.modules.androidndk.filefilters.notc`. Unlike the inclusive filters, the exclusive are tables, because Premake exposes the logical `and` as separate table elements. `premake.modules.androidndk.filefilters.notc` is `{ "files:not **.c", "files:not **.c.arm", "files:not **.c.neon", "files:not **.c.arm.neon" }`, for example. Premake filter tables can be nested, you don't need to perform flattening manually if you want to combine the language filters with additional terms. + +In addition, raw combinations of extensions and ARM suffixes are available in `filefilters` with the `extensions` suffix, such as `premake.modules.androidndk.filefilters.cextensions`, which is `{ ".c", ".c.arm", ".c.neon", ".c.arm.neon" }` (without the `**` recursive wildcard). You can do your own processing of them using functions like `table.translate` and `table.concat`, for instance, to attach all the possible extensions to a specific file name. + +Note that ndk-build treats the uppercase `.C` extension as C++. To avoid imposing additional constraints, this module allows using the `.C` extension for C++, even though other Premake actions may treat it as C, especially considering that `path.hasextension` in Premake is case-insensitive. However, **Premake filters are case-insensitive, and `"files:**.c"` or `"files:**.C"` will match *both* C `.c` and C++ `.C` sources**, leading to the specified settings being used for both C and C++. Therefore, **it's recommended to completely avoid using the uppercase `.C` extension**. The `.C` extension is also not included in the `premake.modules.androidndk.filefilters` constants for this reason. + +It is important that **you must not try to derive a `language` filter from an extension filter** because the `language` setting has a `"project"` scope in Premake rather than `"config"`, and thus it doesn't support file-specific or even configuration- or platform-specific overrides, in addition to not having allowed values for GAS, Yasm and RenderScript, so **the following will not work**: + +```lua +filter(premake.modules.androidndk.cfilefilter) + language("C") -- This WILL NOT WORK! +filter(premake.modules.androidndk.cppfilefilter) + language("C++") -- This WILL NOT WORK! +filter("language:C") + defines({"MY_NULL=((void *)0)"}) +filter("language:C++") + defines({"MY_NULL=0"}) +``` + +In addition, if the `NoPCH` flag is not enabled in the project for the configuration–platform pair, and the file specified in `pchheader` is also listed in `files`, any file-filtered settings for it will be treated as C++ settings. + +With some exceptions, most language-specific settings can be set independently for each language supported by this module. + +- **`buildoptions`** (C, C++, GAS, Yasm, RenderScript) + - One build option may contain one or multiple compiler arguments separated with whitespaces. + - Double quotation marks (`"`) may be used to specify a single compiler argument containing whitespaces. + - If double quotation marks need to be escaped, they need to be prefixed with `\`, as `"\\\""` according to Lua string literal escaping rules. + - The backslash character (`\`) itself also needs to be escaped with another `\`, as `"\\\\"`, so the module can distinguish between a backslash used for escaping the double quote character and an actual backslash character. + - All other characters after GNU make `$` variable or function reference expansion are allowed, and other shell-interpreted characters will be escaped automatically at build time. + - Because of the way the values are gathered from the used filters, duplicate `buildoptions` are eliminated. For this reason, always specify multiple-argument options as `{ "-prefix value1", "-prefix value2" }`, not `{ "-prefix", "value1", "-prefix", "value2" }` (as the latter will become `"-prefix value1 value2"`). + - See the documentation for the `"LinkTimeOptimization"` flag here for details about the handling of `"-flto=…"`. +- **`cdialect`** (C) +- **`cppdialect`** (C++) +- **`flags`** + - `"FatalCompileWarnings"` (C, C++, Yasm) + - `"NoBufferSecurityCheck"` (C, C++) + - `"ShadowedVariables"` (C, C++) + - `"UndefinedIdentifiers"` (C, C++) +- **`defines`** (C, C++, Yasm) + - All `defines` are passed to the compiler before `undefines` even if they're specified both within and without an extension filter. +- **`disablewarnings`** (C, C++, Yasm) +- **`enablewarnings`** (C, C++, Yasm) +- **`fatalwarnings`** (C, C++, Yasm) + - For Yasm, this is treated as `enablewarnings`. +- **`floatingpoint`** (C, C++) + - `"Strict"` is treated as `"Default"`. +- **`floatingpointexceptions`** (C, C++) +- **`forceincludes`** (C, C++, Yasm) + - For C and C++, double quotation mark characters (`"`) are disallowed, as they would terminate the `#include "path"` statement inserted by the compiler. + - For Yasm, `"`, `'`, `;` characters are disallowed. +- **`includedirs`** (**C, C++, GAS and Yasm *combined***, RenderScript separately) + - C, C++, GAS and Yasm use **the same** list of include directories (that is written to `LOCAL_C_INCLUDES`). + - All `includedirs` are passed to the compiler before `sysincludedirs`. + - Whitespaces and `#`, `$` (after GNU make reference expansion) characters are disallowed. +- **`inlinesvisibility`** (C, C++) + - Unlike in other Premake actions using `premake.clang` or `premake.gcc`, this is also supported for C, not only C++. +- **`omitframepointer`** (C, C++) + - To use the Address Sanitizer, this needs to be set to `Off` — see the [Address Sanitizer](https://developer.android.com/ndk/guides/asan) guide on Android Developers. +- **`strictaliasing`** (C, C++) +- **`sysincludedirs`** (**C, C++, GAS and Yasm *combined***, RenderScript separately) + - See `includedirs`. +- **`undefines`** (C, C++, Yasm) + - All `defines` are passed to the compiler before `undefines` even if they're specified both within and without an extension filter. +- **`unsignedchar`** (C, C++) +- **`visibility`** (C, C++) + - Unlike in other Premake actions using `premake.clang` or `premake.gcc`, this is also supported for C, not only C++. +- **`warnings`** (C, C++, Yasm) + - For Yasm, only `"Off"` has effect — other values are treated as warnings enabled as normal. + +### Per-configuration/per-platform for a project, ABI-filtered + +- **`architecture`** + - If an architecture is specified, the settings for this configuration–platform pair will be used will be used while building for the respective ABI if the configuration and the platform are selected for building via `PREMAKE_ANDROIDNDK_CONFIGURATIONS` and `PREMAKE_ANDROIDNDK_PLATFORMS`. In this case, the `architecture` filter may be used to provide ABI-specific settings. + - `"ARM"` for the `armeabi-v7a` ABI. + - `"ARM64"` for the `arm64-v8a` ABI. + - `"x86"` for the `x86` ABI. + - `"x86_64"` for the `x86_64` ABI. + - Leave unspecified or set to `"universal"` to provide fallback settings that will be used for the project for ABIs without a specialization. + - Generally `architecture` should be specified under a `platforms` filter, or, depending on your configuration structure, under a `configurations` one. See the “Usage” section for more information ABI filtering of settings. +- **`exceptionhandling`** = `"Off"` / `"On"` (default) +- **`files`** + - For a built Premake project: + - The following languages are supported: + - C (`.c`) + - C++ (`.cc`, `.cp`, `.cxx`, `.cpp`, `.CPP`, `.c++`, `.C`) + - The list of allowed extensions is broader than the default for Premake v5.0.0-alpha16, but matches the default for the NDK r23). + - Note that while ndk-build (and therefore this setting) is case-sensitive, Premake filters are not. Therefore, a `files:**.c` or a `files:**.C` filter will cause both C `.c` and C++ `.C` to pass, and extension-filtered language-specific settings such as `buildoptions` intended only for C++ will be used for C as well if both are present in the project, for example — see the section about per-language settings. It's better to avoid using the `.C` extension completely. + - GNU Assembler (`.s`, `.S`) + - Yasm (`.asm`) + - `"x86"` and `"x86_64"` architectures only — will be ignored automatically while building for other ABIs, including in architecture-agnostic projects. + - RenderScript (`.rs`, `.fs`) + - `.arm`, `.neon` and `.arm.neon` (but not `.neon.arm`) suffixes are allowed — see the documentation for per-file `armisa` and `vectorextensions` settings, which can be used as an alternative (using file-filtered `armisa` and `vectorextensions` is recommended instead so filters like `"files:**.c"` can still be used rather than `"files:**.c or **.c.arm or **.c.neon or **.c.arm.neon"`, however — though for extension filtering, constants like `premake.modules.androidndk.filefilters.c` are recommended, which include the `.arm` and `.neon` suffixes). + - Whitespaces, backtick, and `"`, `#`, `$` (after GNU make reference expansion), `&`, `'`, `(`, `)`, `,`, `;`, `<`, `>`, `|` characters are disallowed. The backslash is allowed as a path separator on Windows, but disallowed on Linux. + - To create a prebuilt library project or to use an external Android.mk file, specify **only one** `.so` (for the `"SharedLib"` kind), `.a` (for the `"StaticLib"` kind) or `.mk` file, and **no other source files**. See the “Usage” section for more information about the required and supported settings for prebuilt and external projects. + - In prebuilt library paths, whitespaces, and `"`, `#`, `$` (after GNU make reference expansion), `%`, `&`, `*`, `;`, `<`, `>`, `?`, `|` characters are disallowed on Windows. On other operating systems, backtick, `'`, `(`, `)`, `\` characters are not supported. + - In external Android.mk paths, whitespaces are not disallowed. + - All irrelevant file types (such as headers) will be ignored automatically. +- **`flags`** + - `"ExcludeFromBuild"` + - Also supported for excluding individual source files from building. + - `"FatalLinkWarnings"` + - For shared libraries and executables only. + - Unlike in ndk-build itself, fatal link warnings are disabled by default (`LOCAL_DISABLE_FATAL_LINKER_WARNINGS := true`). + - `"LinkTimeOptimization"` + - If used in a static library, shared libraries and executables linked against it will also be linked with link-time optimization (the transitivity ensured for this options, however, doesn't apply to building of source files). + - It's possible to override the default link-time optimization type by specifying the desired link-time optimizations type, such as `-flto=thin`, in **both** `buildoptions` and `linkoptions`. The specific `-flto=` type will also be propagated from `linkoptions` of static library dependencies (though in this case, all linked static libraries must have the same LTO type, and static library dependency analysis only checks `linkoptions`, not `buildoptions`). + - `"NoPCH"` + - Causes `pchheader` to be ignored — see `pchheader` for more information. +- **`formatstringchecks`** (new) = `"Off"` / `"On"` (default) + - Corresponds to `LOCAL_DISABLE_FORMAT_STRING_CHECKS` (but enabling instead of disabling), whether to check `printf` format strings in the sources. +- **`isaextensions`** + - This should be used only in libraries with code used conditionally based on the information provided by `cpuid`, or for emulation purposes, otherwise it may prevent the app from working on physical devices. + - All `"x86"` and `"x86_64"` architecture `isaextensions` normally supported by Premake actions targeting Clang are supported. +- **`libdirs`** + - Search paths for library binaries specific directly via `links`. + - Not recommended as using library binaries directly via linker arguments is discouraged by ndk-build — prefer using prebuilt library projects, and see `links` for more information. + - All `libdirs` gathered from the project and its static library dependencies are passed to the linker before `syslibdirs`. +- **`linkoptions`** + - Most options are supported only in shared libraries and executables, but there are some exceptions which are exported from static libraries as well. + - See `buildoptions` for information about passing multiple arguments, characters that need to be escaped manually, deduplication, disallowed characters. + - Going to Clang++ rather than directly to LLD — `-Wl` may be needed for many options. + - Unlike in other Premake actions, some parsing is done: + - Options prefixed with `-l`, `-L` (with or without `-Wl`), `-Wl,--library`, `-Wl,--library-path` will go to `LOCAL_LDLIBS` instead of `LOCAL_LDFLAGS`, and will be exported from static libraries to dependent shared libraries, similarly to `links`. This may be useful if you have a project named the same as a system library, but you want to link against that system library (for instance, `-llog` if there is a project named `"log"`). See `links` and `libdirs` for more information. + - Options prefixed with `-u` (with or without `-Wl`) or `-Wl,--undefined` will also be exported from static libraries transitively to allow preserving “unused” symbols imported from static libraries in the dependent shared libraries, primarily JNI native function implementations in static libraries — similar to `wholelib`, but more granular, for individual symbols. + - See the documentation for the `"LinkTimeOptimization"` flag here for details about the handling of `"-flto=…"`. +- **`links`** + - Premake projects and system libraries to link against — usable in all kinds of projects (in shared libraries and executables, as well as in static libraries for transitive linkage). + - If a shared library or an executable is linked against a static library project, it will automatically be linked against all system libraries referenced by its static library dependencies, even if static library dependencies are chained via `links`. Transitivity across shared libraries is not provided, however, as it's not necessary and may be undesirable — instead of using `LOCAL_EXPORT_LDLIBS`, the module traverses the dependency tree by itself. This is done for consistency with other Premake actions such as Visual Studio. + - See `wholelib` and `wholelibs` for information about importing static libraries as whole archives. + - If you have a project named the same as a system library, but you still want to link against the system library, you can use a `buildoptions` entry with the `-l` prefix (for instance, `-llog` if there is a project named `"log"`). + - Generally shouldn't be used for anything but linking to other projects and to the NDK system libraries — ndk-build will generate warnings for non-system libraries. Prebuilt library projects (see the “Usage” section) should be preferred for linking against non-system library binaries directly. However, the module still provides the functionality for linking against arbitrary library files: + - Full paths, `libdirs` and `syslibdirs` can be used for specifying search paths (however, there's no way to link against two libraries with the same name under different paths). + - Both short library names and full file names are allowed. If the file name ends with `.a` or `.so`, it's treated as a full name — the `"lib"` prefix must be explicitly specified if needed. + - The `":"` prefix can be added to the file name explicitly to force handling of it as a full file name. +- **`kind`** + - `"SharedLib"` or `"StaticLib"` for a built or a prebuilt shared or static library. + - `"ConsoleApp"` for an executable. + - `"None"` to exclude the project from building for the current configuration/platform. + - Note that `"Makefile"` is not a supported option — an external Android.mk file must be used through `files`, and the project for it must have the `kind` that matches the actual ndk-build script included by it. + - For more information about the usage of prebuilt libraries and external Android.mk files, see the section about them in “Usage”. +- **`renderscriptcompatibility`** (new) = `"Off"` (default) / `"On"` + - Corresponds to `LOCAL_RENDERSCRIPT_COMPATIBILITY`. +- **`renderscriptincludedirsoverride`** (new) = `"Off"` (default) / `"On"` + - Corresponds to the usage of `LOCAL_RENDERSCRIPT_INCLUDES_OVERRIDE` instead of `LOCAL_RENDERSCRIPT_INCLUDES`, whether to ignore the NDK built-in platform and toolchain RenderScript include search paths. +- **`pchheader`** + - Suffixes such as `.arm` and `.neon` are not supported — handling of ARMv7 ISA overrides is performed internally in ndk-build. + - Whitespaces, backtick, and `"`, `#`, `$` (after GNU make reference expansion), `&`, `'`, `(`, `)`, `,`, `;`, `<`, `=`, `>`, `|` characters are disallowed (same as in `files` for sources, but additionally `=`). The backslash is allowed as a path separator on Windows, but disallowed on Linux. + - ndk-build treats the precompiled header as C++ code. The module will configure variables necessary for building C++ code (such as `LOCAL_CPPFLAGS`) if the precompiled header is present, however, if you have any C++-specific settings with a file extension filter, you need to provide at least one source file with the respective extension, otherwise this module will not be able to access those settings. If the file specified in `pchheader` is listed among `files`, any file-filtered settings applied to it will also be treated as C++ settings. + - Ignored if the `"NoPCH"` flag is enabled for the project configuration/platform. +- **`rtti`** = `"Off"` / `"On"` (default) +- **`targetprefix`**, **`targetname`**, **`targetsuffix`** + - Combined into `LOCAL_MODULE_FILENAME`. + - Overriding the binary path or extension is not supported. + - Whitespaces and `#`, `$` (after GNU make reference expansion), `%`, `/`, `;`, `|` characters are disallowed. + - Only removing the `lib` prefix works when invoking ndk-build from Gradle as of Gradle 7.0.2 because it gathers the names of the targets to build from `LOCAL_MODULE_FILENAME` instead of `LOCAL_MODULE`. If executing ndk-build manually without a list of targets, or with a correctly specified list of them, other values are supported. +- **`undefinedsymbols`** (new) = `"Off"` (default) / `"On"` + - For shared libraries and executables only. + - Corresponds to `LOCAL_ALLOW_UNDEFINED_SYMBOLS`, if enabled, errors will not be thrown for undefined symbols, instead, the symbols will be resolved at runtime. +- **`shortcommands`** (new) = `"Off"` (default) / `"On"` + - Corresponds to `LOCAL_SHORT_COMMANDS`, whether to execute build commands from a file rather than directly via the shell, as if the command line is too long, it may not fit in the 8191 character limit on Windows. +- **`syslibdirs`** + - See `libdirs`. +- **`system`** + - Must be `"android"` for the correct library name prefix (`"lib"`). +- **`thinarchive`** (new) = `"Off"` (default) / `"On"` + - For static libraries only. + - Corresponds to `LOCAL_THIN_ARCHIVE`, whether to store only object file paths instead of the object themselves in the static library being built. +- **`vectorextensions`** (for the `"x86"` and `"x86_64"` architectures) + - This should be used only in libraries with code used conditionally based on the information provided by `cpuid`, or for emulation purposes, otherwise it may prevent the app from working on physical devices. + - Allowed values on the `"x86"` architecture: `"SSE4.1"`, `"SSE4.2"` (new), `"AVX"`, `"AVX2"` (SSSE3 and below are required by Android and are always supported). + - Allowed values on the `"x86_64"` architecture: `"AVX"`, `"AVX2"` (SSE4.2 and below are required by Android and are always supported). + - For the allowed values on the `"ARM"` architecture, see the per-file settings section. +- **`wholelib`** (new) = `"Off"` (default) / `"On"` + - For static libraries only. + - Whether all object files from this static library should be included in the shared libraries (but not executables) referencing it (and this library will be added to `LOCAL_WHOLE_STATIC_LIBRARIES` instead of `LOCAL_STATIC_LIBRARIES`). This is useful, for instance, when exporting JNI native function implementations from a static library. + - This setting is for **exporting** the requirement that the static library must be linked as a whole archive — for the equivalent for importing, see `wholelibs` (a static library will be linked via `LOCAL_WHOLE_STATIC_LIBRARIES` rather than `LOCAL_STATIC_LIBRARIES` if it enables `wholelib` for itself, or if it's specified in `wholelibs` of the project importing it — you only need either to specify `wholelib` in the dependency or to add it to `wholelibs` of the dependent projects; though specifying both the same time is fine, that's redundant). +- **`wholelibs`** (new) + - List of static library project names among the `links` of the project to link as whole archives. + - For more information about whole static libraries, see `wholelib` — this setting provides the same functionality, but for **importing** static library dependencies as whole archives (only one of `wholelib` or `wholelibs` is enough). + - If a project is listed in `wholelibs`, it still needs to be added to `links` to be linked — this setting is merely a modifier, and therefore it's fine to just list all static library projects that need to be linked as whole archives once for the entire workspace (though `wholelib` is more suited for this purpose). + +### Per-configuration/per-platform for a workspace, not ABI-filtered + +These settings have effect on `APP` variables in the Application.mk rather than `LOCAL` variables in projects Android.mk files. + +There is a single Application.mk file for the entire workspace, and the same values of the `APP` variables are used regardless of the current target ABI. Thus, **for all configurations and platforms selected in `PREMAKE_ANDROIDNDK_CONFIGURATIONS` and `PREMAKE_ANDROIDNDK_PLATFORMS`, the values of these settings must be the same for**: + +- All projects in the workspace; +- All target ABIs (`architecture`s). + +For all selected configurations (which are visited in an undefined order), the module tries to locate the value in: + +1. The workspace itself. +2. The `startproject`. +3. For all selected platforms, visited in an undefined order, platform-specific projects, also in an undefined order. +4. Platform-agnostic projects, in an undefined order. + +You can use, for instance, `optimize("Off")` in the `"Debug"` configuration, and `optimize("On")` in the `"Release"` one, as long as you build the workspace with **either** `PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Debug` or `PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Release`, but not both at once. However, you can't disable optimizations for specific projects in the `"Release"` configuration in this case. + +- **`cppstl`** (new) = `"Default"` / `"c++"` / `"none"` / `"system"` + - The C++ standard library to use. + - NDK versions before r18 also support `"gabi++"`, `"gnustl"`, `"stlport"`. + - Use `staticruntime` to specify whether to link against the static standard library or against the shared one. +- **`optimize`** + - Handled in a way that approximates the behavior of other Premake actions. + - Unspecified, `"Off"`, `"Debug"` result in `APP_OPTIM := debug`. + - Anything else (`"Size"`, `"Speed"`, `"Full"`) results in `APP_OPTIM := release`, and also force-disables `APP_DEBUG`. +- **`symbols`** + - Handled in a way that approximates the behavior of other Premake actions. + - Ignored if `optimize` is set to a value that results in optimization enabled. + - Unspecified, `"Off"`, `"Default"` result in `APP_DEBUG := true`. + - Anything else (`"On"`, `"FastLink"`, `"Full"`) results in `APP_DEBUG := false`. +- **`staticruntime`** = `"Default"` / `"Off"` / `"On"` (default) + - Whether to use the static C++ standard library (`cppstl`) as opposed to a shared one. + - Static by default, consistent with the default CMake behavior (where `c++_static` is the default standard library). + - Ignored for `"none"` and `"system"` standard libraries. +- **`systemversion`** + - Minimum Android SDK version required by the application, as a string representation of a number (corresponds to `APP_PLATFORM := android-systemversion`). + +### Per-project or per-workspace + +- **`basedir`** +- **`configurations`** + - Whitespaces and `%`, `(`, `)`, `,` characters are disallowed (configuration and platform names are used in GNU make list function calls). +- **`location`** + - The directory where the Android.mk file for the project or the Application.mk and the root Android.mk for the workspace will be placed (if provided — otherwise `basedir` will be used). + - For projects, it is heavily recommended to set this to a folder that doesn't contain header files. This is the path that will be used as `LOCAL_PATH`, and ndk-build internally adds `LOCAL_PATH` as the last include directory, causing `LOCAL_PATH` to be treated as a more “system” include search path than the system paths themselves. This causes issues when the `LOCAL_PATH` directory contains header files named the same as C and C++ standard library headers — the LLVM libc++ uses `#include_next` in a few files (such as `math.h`), causing project's files to included from system headers; this is especially dangerous if the local `"math.h"` itself `#include`s ``. +- **`platforms`** + - See `configurations`. +- **`project`** + - Whitespaces and `#`, `$`, `%`, `/`, `:`, `;`, `=`, `|` characters are disallowed. + - GNU make variable or function references (`$`) are disallowed. +- **`startproject`** + - The start project will have a higher priority when locating setting values for Application.mk variables. +- **`workspace`** + - Whitespaces and `#`, `$` characters are disallowed. + - GNU make variable or function references (`$`) are disallowed. + +## Future work, contributing and omissions + +Several features were left temporarily or intentionally unimplemented in the current version of the module, however, they may be useful in certain cases. + +If your project depends on something missing from the module, feel free to open an issue, and we'll discuss how the needed functionality can be added to the module in a convenient and flexible way — or submit a pull request with the implementation! + +Note that the general preference is to use existing Premake settings wherever possible — potentially twisting them and adding the necessary constraints while still trying to keep the original semantic concepts — instead of adding new ones. Don't hesitate to exploit filters, to interpret a variable in different ways depending on the target ABI and overall context, to use any opportunity to make the module more compatible with existing projects for other targets. However, if new settings need to be added, prefer using `kind = "boolean"` settings rather than flags as the former have three states, making it easier to provide default behavior, especially if the new settings are promoted to the Premake core or used in other modules in the future, potentially on platforms with different defaults. + +If you're adding functionality that involves strings in a new place in the generated Android.mk and Application.mk files, make sure to check which non-letter ASCII characters, including different types of whitespaces, are supported by ndk-build in the specific context, especially if it's a file name or a path, on both Linux (which allows any characters in paths) and Windows (including UNC paths), with attention to backslash-escaping of shell-interpreted characters (see `androidndk.shellEscapedCharactersPostQuotes`), but not only them. All user input must be passed through `p.esc`, preferably early (and functions expecting output of `p.esc` as arguments must be named with the `PostEsc` suffix), to ensure the integrity of GNU make variable or function references (also make sure that paths including `$` references are handled correctly as absolute or as relative depending on the context, especially when joining — usually paths beginning with `$` should be considered absolute, and that's how Premake itself treats them, while a relative path needs to start with `./$` in this case) and of escaping of the comment character `#`. After `p.esc`, the invalid character check may be done for the non-reference part of the string if needed — see the usage `androidndk.staticallyHasPatternPostEsc` (build-time validation would be an overkill if there are references in the string, but to catch potential errors when porting projects from other platforms, generation-time checks are useful). In many places, strings are used in shell commands invoked by ndk-build — see how checkers and escapers starting with `androidndk.shellEscape` are used in the module; also shell character escaping needs to be done at build time, not at Premake invocation time, since it must be performed after expanding GNU make references. Also, if you're passing user input to a GNU make function, use an intermediate variable (see `androidndk.getTemporaryVariableReference`), as the user-provided string may contain brackets or commas, which would break the parsing of the function call. + +The following functionality has been omitted currently, but may be nice to have for reasons like compatibility: + +- Unit tests. This is a large module with complex input handling with a large number of edge cases for many settings that, in some cases, depend on the kind of the project, the target ABIs, source languages used in the project — written in a dynamically-typed language. Many errors and typos may occur. Tests verifying both supported and disallowed input need to be added — covering platform-dependent and platform-agnostic projects, ABI-dependent and ABI-agnostic projects, different kinds of binaries, prebuilt libraries, external Android.mk projects, source files written in various languages with extension filters and `.arm` and `.neon` suffixes, link settings and transitivity of them across chains of static libraries where needed, GNU make variable and function references, disallowed characters, shell-interpreted character escaping, and other cases handled by this module. +- Building for deprecated ABIs removed from the NDK — `armeabi`, `armeabi-v7a-hard`, `mips`, `mips64`. While simply adding a new `architecture` is trivial (though for choosing `armeabi-v7a-hard`, a special setting for the floating-point argument convention may be more suitable), that wouldn't imply complete support for the details of them — such as instruction set extensions (like the MXU vector extensions for MIPS), hardware floating-point unit support. MIPS-powered Android devices are extremely rare, and ARMv5 deprecation began in Android 4.0, having been completed in Android 4.4. +- Support for obsolete toolchains removed from the NDK, such as GCC, as well as toolchain version selection (as of r23, the NDK only offers one version of the Clang toolchain). +- Clang-Tidy — needs to be integrated into the architecture of Premake itself. +- Post-processing of the generated machine code and assembly source files via `LOCAL_FILTER_ASM` — needs to be added in a way friendly to the Premake architecture. +- Compatibility with the Visual Studio actions for Android (the built-in `android` module). It defines many settings in ways that aren't consistent with the Premake core itself, as well as adding redundant options: + - Values are generally lowercase, while the Premake core uses PascalCase internally. + - The additions to the list of the allowed `architecture` values are chaotic. For `armeabi`, it allows both `"arm"` and `"armv5"` (which are registered as separate options rather than aliases of each other), while for `armeabi-v7a`, it uses `"armv7"`. Premake itself provides an `"ARM"` option, which, given the removal of ARMv5 support from the NDK, is more likely to be expected to correspond to ARMv7 as opposed to ARMv5. `arm64-v8a` is exposed as `"aarch64"` instead of `"ARM64"` used in the Premake core. + - ARMv5 and ARMv7 instruction sets are presented as two options: a `"Thumb"` flag, and a `"thumbmode"` setting, with one of the allowed values being `"disabled"` rather than `"Default"` commonly used in Premake. + - `stl` is a new setting in it, but it uses misleading names. The `"gnustl"` and `"c++"` standard libraries provided by ndk-build are called `"gnu"` and `"libc++"` instead, which is likely not what users expect. For `"system"`, the value `"none"` is used — however, the NDK has both `"system"` (which is deprecated) and `"none"`, the difference between the two being `new` and `delete` being supported in `"system"`, while `"none"` providing no C++ standard library functionality at all. The name `stl` also doesn't imply C++ directly, which may cause issues if Premake gets support for a different language that also has the standard library referred to by the same acronym — unlike settings like `buildoptions`, this may be even workspace-level (and in ndk-build, it is), and thus the value is not file- or extension-filterable. + - `androidapilevel` is redundant due to the existence of `systemversion`. + - The `"posix"` system tag is not added for Android. +- The “call array” architecture is not used in the code as certain settings are handled in multiple places in different ways, and various kinds of preprocessing are done. Replacing parts of the generation code in project scripts is unlikely to be useful or convenient for this reason. diff --git a/src_rebuild/premake_modules/androidndk/UNLICENSE.txt b/src_rebuild/premake_modules/androidndk/UNLICENSE.txt new file mode 100644 index 00000000..68a49daa --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/UNLICENSE.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/src_rebuild/premake_modules/androidndk/_manifest.lua b/src_rebuild/premake_modules/androidndk/_manifest.lua new file mode 100644 index 00000000..01def5e8 --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/_manifest.lua @@ -0,0 +1,11 @@ +-- premake-androidndk - a multi-ABI, multi-language ndk-build Android.mk generator for Premake. +-- Originally written by Vitaliy Kuzmin. +-- See README.md for detailed usage and design information. +-- Available under the Unlicense (see UNLICENSE.txt) or the BSD 3-Clause "New" or "Revised" License (see LICENSE.txt). + +return { + "_preload.lua", + "androidndk.lua", + "androidndk_project.lua", + "androidndk_workspace.lua", +}; diff --git a/src_rebuild/premake_modules/androidndk/_preload.lua b/src_rebuild/premake_modules/androidndk/_preload.lua new file mode 100644 index 00000000..e66cfbfb --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/_preload.lua @@ -0,0 +1,110 @@ +-- premake-androidndk - a multi-ABI, multi-language ndk-build Android.mk generator for Premake. +-- Originally written by Vitaliy Kuzmin. +-- See README.md for detailed usage and design information. +-- Available under the Unlicense (see UNLICENSE.txt) or the BSD 3-Clause "New" or "Revised" License (see LICENSE.txt). + +local p = premake; + +p.api.addAllowed("system", p.ANDROID); +-- NEON is enabled by default, like in NDK r21. +-- Similar to how Premake defines "IA32" for x86, use "ARMv7" to disable NEON (and use the .neon suffix where needed). +p.api.addAllowed("vectorextensions", { "ARMv7", "NEON", "SSE4.2" }); +if os.systemTags[p.ANDROID] == nil then + os.systemTags[p.ANDROID] = { "android", "posix", "mobile" }; +end + +p.api.register({ + name = "armisa", + scope = "config", + kind = "string", + allowed = { + p.DEFAULT, + "A32", + "T32", + }, +}); + +p.api.register({ + name = "cppstl", + scope = "config", + kind = "string", + allowed = { + p.DEFAULT, + -- As of NDK r18, only none, c++ and system are available, with the latter being deprecated. + -- Supporting the older STLs is trivial, however. + "c++", + "gabi++", + "gnustl", + "none", + "stlport", + "system", + }, +}); + +p.api.register({ + name = "formatstringchecks", + scope = "config", + kind = "boolean", +}); + +p.api.register({ + name = "renderscriptcompatibility", + scope = "config", + kind = "boolean", +}); + +p.api.register({ + name = "renderscriptincludedirsoverride", + scope = "config", + kind = "boolean", +}); + +p.api.register({ + name = "shortcommands", + scope = "config", + kind = "boolean", +}); + +p.api.register({ + name = "thinarchive", + scope = "config", + kind = "boolean", +}); + +p.api.register({ + name = "undefinedsymbols", + scope = "config", + kind = "boolean", +}); + +-- Whether this static library project should be linked as a whole, even if not specified in `wholelibs` of dependent projects. +p.api.register({ + name = "wholelib", + scope = "config", + kind = "boolean", +}); + +-- Subset of `links` which need to be linked as a whole regardless of their `wholelib` setting. +p.api.register({ + name = "wholelibs", + scope = "config", + kind = "list:mixed", + tokens = true, +}); + +newaction({ + trigger = "androidndk", + shortname = "Android ndk-build", + description = "Generate Android ndk-build makefiles", + targetos = p.ANDROID, + valid_kinds = { p.CONSOLEAPP, p.SHAREDLIB, p.STATICLIB }, + valid_languages = { p.C, p.CPP }, + -- Premake workspace and project names can include periods, so using suffixes for both. + onWorkspace = p.modules.androidndk.onWorkspace, + onProject = p.modules.androidndk.onProject, +}); + +-- Decide when the full module should be loaded. +return function(config) + return _ACTION == "androidndk"; +end; diff --git a/src_rebuild/premake_modules/androidndk/androidndk.lua b/src_rebuild/premake_modules/androidndk/androidndk.lua new file mode 100644 index 00000000..549e9915 --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/androidndk.lua @@ -0,0 +1,465 @@ +-- premake-androidndk - a multi-ABI, multi-language ndk-build Android.mk generator for Premake. +-- Originally written by Vitaliy Kuzmin. +-- See README.md for detailed usage and design information. +-- Available under the Unlicense (see UNLICENSE.txt) or the BSD 3-Clause "New" or "Revised" License (see LICENSE.txt). + +local p = premake; +p.modules.androidndk = {}; +local androidndk = p.modules.androidndk; +androidndk._VERSION = "1.0.0"; + +include("androidndk_project.lua"); +include("androidndk_workspace.lua"); + +include("_preload.lua"); + +-- Inserts one or multiple values into a table. +-- Useful for inserting compiler flags from built-in Premake toolsets. +function androidndk.insertKeyedFlat(tbl, value) + -- Skip if the value is nil, for instance, to quickly ignore irrelevant flags or values of settings without a mapping. + if value == nil then + return; + end + if type(value) == "table" then + for i, element in ipairs(value) do + androidndk.insertKeyedFlat(tbl, element); + end + return; + end + if tbl[value] ~= nil then + return; + end + table.insert(tbl, value); + tbl[value] = value; +end + +-- Rules regarding special characters in the input and how they're handled in this module (as of NDK r23, checked with `$(warning "$(VARIABLE)")`): +-- * Whitespaces (tab, vertical tab, form feed, space) - defining the word "whitespace" as all of these, unlike just "space": +-- * Horizontal tab being the first on a line defines a recipe. +-- * Otherwise, all whitespace characters behave like whitespaces, at least in the syntax of GNU make itself. +-- * Trimming behavior in various contexts relevant to Android.mk: +-- * All sides of a variable assignment - left-hand, the operator itself, right-hand - have leading whitespaces trimmed. +-- * In the right-hand side of a variable assignment, each line (separated with a \-escaped newline) is treated as a list element, like a complete token. +-- Any amount (including zero) of whitespaces or escaped newlines between two non-empty elements is collapsed into a single " " space. +-- However, if the last non-whitespace character in an element is $ (not $$, but $ alone), no space is inserted, they're just removed (including the $). +-- This rule, however, is directly applicable only to inner newline separators, not the beginning or the end of the whole right-hand side. +-- Whitespaces and newlines before the first non-empty element are trimmed (as leading whitespaces in general). +-- Behavior for the last element is less consistent. +-- If the last non-empty element is followed only by whitespaces, all those trailing whitespaces are preserved as their original encoding. +-- If there's a \-escaped newline after the last non-empty element, however, all trailing whitespaces and newlines are collapsed into one " " space. +-- Within the boundaries of one list element, however, all original whitespaces are preserved. +-- * Function call argument lists (as a whole, not individual arguments) have leading whitespaces trimmed. +-- * $() can be prepended or appended to include leading or trailing whitespaces in an element or the first function argument. +-- * Resolution: +-- * Pass through directly. +-- * Single-line variable generation: +-- * Preserve project-provided leading whitespaces for the whole value (or the first table element) using $(). +-- * Multiline list variable generation: +-- * Preserve project-provided leading and trailing whitespaces for each table element using $(), though not necessary for the last. +-- * Both "\" and " \" are fine for separating table elements. +-- However, using " \" is consistent with the general recipe line splitting rules of GNU make for joining with a space. +-- * Newline characters (carriage return, line feed): +-- * Start a new statement. +-- * Can be escaped with a backslash, but then the newline itself may be trimmed according to the whitespace trimming rules. +-- * True newlines can be kept in variable declarations using define/endef. +-- * Resolution: +-- * Replace unconditionally with spaces. +-- * Escaping handled separately. +-- * Not using define/endef until a real use in ndk-build scripts is found. +-- * Backslash: +-- * If the last character before a newline, behaves as a line split, trimmed according to the whitespace trimming rules. +-- * Escapes the number sign which starts a comment. +-- * In other cases, passed as a raw character. +-- * Resolution: +-- * Pass through directly. +-- * If the last character on a line and provided by the project, append $(). +-- * Dollar sign: +-- * A single dollar sign is a prefix for a variable or a function reference. +-- * $(name arguments) and ${name arguments} are the regular syntax. +-- * If neither () nor {} are used, one character immediately following the $ is used as the variable name. +-- * $ alone directly in the end of a variable assignment is treated as a raw $ character. +-- * The effect of a trailing $ on variable assignments split into multiple lines is described in the section about whitespaces. +-- * $$ results in a raw $ character. +-- * Resolution: +-- * Pass through directly to allow using environment variables and built-in variables like TARGET_ARCH_ABI in scripts, as well as functions. +-- * Prevent unclosed variable and function references with an undefined, but safe result. +-- The easiest way is to close them - doesn't require insertion into the middle - with a special case for the trailing $ (convert to $$). +-- * Number sign: +-- * Starts a comment. +-- * Escaped with a backslash for passing as raw data, but the backslash itself may be escaped as well. +-- * Backslashes preceding a # are treated as n/2 backslashes ((n-1)/2 if there is an odd number of them) in other cases. +-- * An even number of backslashes before the # in the original file causes the comment not to be escaped. +-- * Resolution: +-- * Escape with a single backslash and a terminator to prevent dependence on the number of backslashes, as $()\#. +-- * This makes concatenation of a string ending with \ and one starting with an escaped # safe. + +androidndk.commentEscapeString = "$()\\#"; +androidndk.commentUnescapePattern = string.escapepattern(androidndk.commentEscapeString); + +androidndk.whitespacesNonNewline = { + ["\t"] = "\t", + ["\v"] = "\v", + ["\f"] = "\f", + [" "] = " ", +}; + +-- Due to escaping of #, this function can't be called again on its result. +function androidndk.esc(value) + value = string.gsub(value, "#", androidndk.commentEscapeString); + -- Replace newlines with safer spaces. + value = string.gsub(value, "\r\n", " "); + value = string.gsub(value, "\n", " "); + value = string.gsub(value, "\r", " "); + -- Make sure there are no unclosed variable and function references. + -- Close $() and ${} with a bracket, and escape the final $. + -- As a result, all references will be closed and balanced. + local unclosedReferenceTerminators = {}; + local afterReferenceCharacter = false; + -- Need a while loop as the for loop counter can't be modified from the body. + local i = 1; + while i <= string.len(value) do + local character = string.sub(value, i, i); + if character == "\\" and string.sub(value, i + 1, i + 1) == "#" then + -- Escaped comment character. + i = i + 1; + character = "#"; + end + if afterReferenceCharacter then + -- The character after the $. + -- Options: $ (escaped raw $), an opening bracket (opening a reference), any other character (single-character reference). + if character == "(" then + table.insert(unclosedReferenceTerminators, ")"); + elseif character == "{" then + table.insert(unclosedReferenceTerminators, "}"); + end + afterReferenceCharacter = false; + else + -- Not the character after the $. + -- Options: $, a reference-closing bracket, any other raw character. + if character == "$" then + afterReferenceCharacter = true; + elseif #unclosedReferenceTerminators > 0 and character == unclosedReferenceTerminators[#unclosedReferenceTerminators] then + unclosedReferenceTerminators[#unclosedReferenceTerminators] = nil; + end + end + i = i + 1; + end + local referencesClosed = { value }; + -- Escape the incomplete single-character reference as a raw $. + if afterReferenceCharacter then + table.insert(referencesClosed, "$"); + end + -- Close the unclosed references. + for i2 = 1, #unclosedReferenceTerminators do + table.insert(referencesClosed, unclosedReferenceTerminators[#unclosedReferenceTerminators - i + 1]); + end + return table.concat(referencesClosed); +end + +function androidndk.setupGeneration() + -- The tab character is the recipe line prefix in makefiles. + p.indent(" "); + p.escaper(androidndk.esc); + p.eol("\n"); +end + +-- Whether the string contains GNU make variable or function references. +-- p.esc should be done prior to this. +-- Empty references, that are $() or ${}, are not treated as references, merely as delimiters. +-- $() is used in comment sign escaping, for example. +function androidndk.hasNonEmptyMakeReferencesPostEsc(value) + -- For strictness and simplicity, the lone trailing $ is treated as a reference too (can't be a result of p.esc anyway). + local i = 0; + while true do + i = string.find(value, "%$", i + 1); + if i == nil then + return false; + end + if string.sub(value, i, i + 1) == "$$" then + i = i + 1; + else + local potentiallyEmptyReference = string.sub(value, i, i + 2); + if potentiallyEmptyReference == "$()" or potentiallyEmptyReference == "${}" then + i = i + 2; + else + return true; + end + end + end +end + +-- Removes variable and function references and reverts p.esc's escaping of # and $ from a GNU make variable value (but not multiline). +-- Useful for coarse validation of string contents, whether there are unsupported characters. +-- What the references expand to isn't known at Premake time, but static value validation may be good to catch common issues. +-- To get a value that can be passed back to GNU make, do string.gsub of the "%$" pattern to "$$", and then p.esc. +-- For strictness and simplicity, the lone trailing $ is treated as a reference too (can't be a result of p.esc anyway). +function androidndk.unescapeAndRemoveMakeReferencesPostEsc(value) + -- Unescape p.esc's escaping of the comment character. + value = string.gsub(value, androidndk.commentUnescapePattern, "#"); + local newValue = {}; + local unclosedReferenceTerminators = {}; + local afterReferenceCharacter = false; + for i = 1, string.len(value) do + local character = string.sub(value, i, i); + if afterReferenceCharacter then + -- The character after the $. + -- Options: $ (escaped raw $), an opening bracket (opening a reference), any other character (single-character reference). + if character == "$" then + if not (#unclosedReferenceTerminators > 0) then + -- Escaped $. + table.insert(newValue, "$"); + end + elseif character == "(" then + table.insert(unclosedReferenceTerminators, ")"); + elseif character == "{" then + table.insert(unclosedReferenceTerminators, "}"); + end + afterReferenceCharacter = false; + else + -- Not the character after the $. + -- Options: $, a reference-closing bracket, any other raw character. + if character == "$" then + afterReferenceCharacter = true; + else + if #unclosedReferenceTerminators > 0 then + if character == unclosedReferenceTerminators[#unclosedReferenceTerminators] then + unclosedReferenceTerminators[#unclosedReferenceTerminators] = nil; + end + else + -- A raw character outside references. + table.insert(newValue, character); + end + end + end + end + return table.concat(newValue); +end + +function androidndk.staticallyHasPatternPostEsc(value, pattern) + return string.find(androidndk.unescapeAndRemoveMakeReferencesPostEsc(value, true), pattern) ~= nil; +end + +-- Characters that can be escaped with a backslash in Windows shell commands, except for \ itself and ". +-- \ and " should be escaped before these inside individual arguments as they may be wrapped in " by the module itself to include whitespaces. +-- On Windows, before other characters (except for \ and "), a \ is kept and treated as a directory separator instead. +-- Extracted by using all characters 33-126 (with special rules for #) prefixed with \ in a -Define and seeing the Clang error for its usage. +-- Matches Windows sh_chars_sh in GNU make, with the addition of a single quote. +-- On Linux, where \ is not a path separator, prefixing any character with \ results in it being escaped in the shell. +androidndk.shellEscapedCharactersPostQuotes = { + "#", "$", "&", "'", "(", ")", "*", ";", "<", ">", "?", "[", "]", "^", "`", "{", "|", "}" +}; + +-- Checkers are meant to be used in assignToVariablePostEsc calls, and assume the input is pre-escaped and doesn't contain non-empty references. + +function androidndk.shellEscapePostQuotesChecker(value) + -- Comments are escaped as $()\# - contains an empty reference. + -- However, this still includes the # itself, which needs to be escaped with a backslash in a shell command. + -- Raw dollar signs ($$ representing $) need to be escaped in a shell command too. + if string.find(value, "#") ~= nil or string.find(value, "%$%$") ~= nil then + return true; + end + -- Remove empty references to check if the value contains (){}. + -- It's assumed that these are the only kind of references that may be passed to checkers. + value = string.gsub(string.gsub(value, "%$%(%)", ""), "%${}", ""); + return string.find(value, "[&'%(%)%*;<>%?%[%]%^`{|}]") ~= nil; +end + +function androidndk.shellEscapeChecker(value) + -- The only two usages of a backslash after androidndk.esc are a raw backslash and a # escape sequence. + -- Both \ and # need to be escaped with \ in the shell. + -- So, it's okay to return true if there is any \ at all. + if string.find(value, "[\\\"]") ~= nil then + return true; + end + return androidndk.shellEscapePostQuotesChecker(value); +end + +function androidndk.shellEscapeModuleFileNameChecker(value) + -- : must be \-escaped on Linux. + -- It's a drive letter separator on Windows, however, so it's disallowed in LOCAL_MODULE_FILENAME there (it's a name, not a path). + if string.find(value, "[:]") ~= nil then + return true; + end + return androidndk.shellEscapeChecker(value); +end + +function androidndk.shellEscapeSrcFilesChecker(value) + -- : must be \-escaped on Linux, but cannot be escaped on non-Cygwin Windows, and is a drive letter separator there supported directly. + -- The differences are handled at build time according to $(HOST_OS). + if string.find(value, "[:]") ~= nil then + return true; + end + return androidndk.shellEscapeChecker(value); +end + +-- Using UPPER_CASE names because ndk-build reserves all lower-case names. +androidndk.shellEscapePreQuotesMakeCall = "PREMAKE_ANDROIDNDK_SHELL_ESCAPE_PRE_QUOTES"; +androidndk.shellEscapePostQuotesMakeCall = "PREMAKE_ANDROIDNDK_SHELL_ESCAPE_POST_QUOTES"; +androidndk.shellEscapeMakeCall = "PREMAKE_ANDROIDNDK_SHELL_ESCAPE"; +androidndk.shellEscapeModuleFileNameMakeCall = "PREMAKE_ANDROIDNDK_SHELL_ESCAPE_MODULE_FILENAME"; +androidndk.shellEscapeSrcFilesMakeCall = "PREMAKE_ANDROIDNDK_SHELL_ESCAPE_SRC_FILES"; + +androidndk.temporaryVariablePrefix = "PREMAKE_ANDROIDNDK_TEMPORARY_"; + +-- Gets or adds a temporary intermediate GNU make variable. +-- Useful for wrapping strings in GNU make function calls, for safety if they contain the closing bracket. +function androidndk.getTemporaryVariableReference(temporaryVariables, value) + local index = temporaryVariables[value]; + if index == nil then + index = #temporaryVariables + 1; + temporaryVariables[index] = value; + temporaryVariables[value] = index; + end + return "$(" .. androidndk.temporaryVariablePrefix .. index .. ")"; +end + +function androidndk.getCaseElse(anyCasesWritten) + if anyCasesWritten then + return "else "; + end + return ""; +end + +-- ABIs that be set by a combination of settings that can be expressed in a project. +-- With the architecture set to "universal" or nil, projects can be built for ABIs not in this list though. +-- If new ABIs are added to the NDK, they can be supported without updating this module at least in ABI-agnostic projects. +-- Sorted alphabetically. +androidndk.knownAbis = { + "arm64-v8a", + -- "armeabi" (ARMv5) was removed in NDK r17. + "armeabi-v7a", + -- "armeabi-v7a-hard" was removed in NDK r12. + -- Building for both calling conventions at once (if passing this table to APP_ABI) would be pointless anyway. + -- "mips" and "mips64" were removed in NDK r17. + -- Premake has no built-in architecture values for MIPS. + "x86", + "x86_64", +}; + +androidndk.ABI_ALL = "all"; + +androidndk.architectureAbis = { + [p.ARM] = "armeabi-v7a", + [p.ARM64] = "arm64-v8a", + [p.X86] = "x86", + [p.X86_64] = "x86_64", +}; + +-- Returns: +-- * The ABI name for supported configurations. +-- * "all" (like the "all" that can be passed to APP_ABI) if the configuration doesn't have an ABI filter. +-- * nil for unsupported configurations. +function androidndk.getConfigAbi(config) + -- While armeabi (ARMv5) and armeabi-v7a (ARMv7) may be both considered "ARM", ARMv5 is different enough to have its own `architecture` option. + -- Support for ARMv5, however, was removed in NDK r17. + -- If needed, it can be exposed via a separate configuration variable. + -- MIPS support was also removed in r17. + if config.architecture == nil or config.architecture == p.UNIVERSAL then + return androidndk.ABI_ALL; + end + return androidndk.architectureAbis[config.architecture]; +end + +function androidndk.preventWhitespaceTrimming(value) + if androidndk.whitespacesNonNewline[string.sub(value, 1, 1)] ~= nil then + -- Prevent trimming of the leading whitespaces. + value = "$()" .. value; + end + local trailingCharacter = string.sub(value, -1, -1); + if trailingCharacter == "\\" or androidndk.whitespacesNonNewline[trailingCharacter] ~= nil then + -- Don't treat the final backslash (like in a directory path) as line splitting. + -- Also prevent trimming of the trailing whitespaces. + value = value .. "$()"; + end + return value; +end + +-- Returns whether the post-processing call is needed for any element. +function androidndk.assignToVariablePostEsc(name, value, multiline, postProcessChecker, operator) + if operator == nil then + operator = ":="; + end + if type(value) == "string" then + value = { value }; + end + value = table.filterempty(value); + if not (#value > 0) then + return false; + end + local postProcessNeeded = false; + if multiline then + p.push("%s %s \\", name, operator); + for i, element in ipairs(value) do + local boundedLine = androidndk.preventWhitespaceTrimming(element); + if i >= #value then + p.w(boundedLine); + else + p.w("%s \\", boundedLine); + end + -- Post-processing is always needed if there are references because they are expanded to anything at runtime. + -- However, if not needed, don't waste build time doing unnecessary substitutions. + if postProcessChecker ~= nil and not postProcessNeeded and (androidndk.hasNonEmptyMakeReferencesPostEsc(element) or postProcessChecker(element)) then + postProcessNeeded = true; + end + end + p.pop(); + else + -- Inner whitespaces are preserved within the right-hand side of an assignment, safe to concatenate. + p.w("%s %s %s", name, operator, androidndk.preventWhitespaceTrimming(table.concat(value, " "))); + end + return postProcessNeeded; +end + +function androidndk.writePostProcessCall(variableName, makeCall) + p.w("%s := $(call %s,$(%s))", variableName, makeCall, variableName); +end + +function androidndk.isConfigSupported(config, reportWarnings) + if config.flags.ExcludeFromBuild then + -- Intentionally excluded. + -- Warnings specific to this module shouldn't be reported either as the configuration may not be targeting this module at all. + return false; + end + if config.project ~= nil then + assert(androidndk.isProjectSupported(config.project, false), "Usage of configurations for unsupported projects must not be attempted at all"); + end + -- Wrong kind or ABI are a normal situation when the project has both Android and non-Android configs. + -- Not reporting warnings for them (or any warnings as for non-Android targets these issues may be non-existent). + if androidndk.kindScripts[config.kind] == nil or androidndk.getConfigAbi(config) == nil then + return false; + end + local warningLocation; + if reportWarnings then + if config.project ~= nil then + warningLocation = string.format("workspace '%s' project '%s' configuration '%s'", config.workspace.name, config.project.name, config.name); + else + warningLocation = string.format("workspace '%s' configuration '%s'", config.workspace.name, config.name); + end + end + local configSupported = true; + -- Whitespaces because configurations are placed in a whitespace-separated list. + -- % because configurations and platforms are used in filter and filter-out, where % is a wildcard. + -- It can be escaped with \, but then the case when the % is preceded by other backslashes is complicated to handle. + -- The rest are disallowed due to usage of the names in GNU make function calls. + local disallowedCharacters = "%(),"; + local disallowedPattern = "[%s%%%(%),]"; + if androidndk.staticallyHasPatternPostEsc(p.esc(config.buildcfg), disallowedPattern) then + configSupported = false; + if reportWarnings then + p.warn( + "Skipping %s with configuration name containing whitespaces or disallowed characters %s", + warningLocation, disallowedCharacters); + end + end + if config.platform ~= nil and androidndk.staticallyHasPatternPostEsc(p.esc(config.platform), disallowedPattern) then + configSupported = false; + if reportWarnings then + p.warn( + "Skipping %s with platform name containing whitespaces or disallowed characters %s", + warningLocation, disallowedCharacters); + end + end + return configSupported; +end + +return androidndk; diff --git a/src_rebuild/premake_modules/androidndk/androidndk_project.lua b/src_rebuild/premake_modules/androidndk/androidndk_project.lua new file mode 100644 index 00000000..b310ecad --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/androidndk_project.lua @@ -0,0 +1,1743 @@ +-- premake-androidndk - a multi-ABI, multi-language ndk-build Android.mk generator for Premake. +-- Originally written by Vitaliy Kuzmin. +-- See README.md for detailed usage and design information. +-- Available under the Unlicense (see UNLICENSE.txt) or the BSD 3-Clause "New" or "Revised" License (see LICENSE.txt). + +local p = premake; +local androidndk = p.modules.androidndk; + +androidndk.kindScripts = { + [p.CONSOLEAPP] = "BUILD_EXECUTABLE", + [p.SHAREDLIB] = "BUILD_SHARED_LIBRARY", + [p.STATICLIB] = "BUILD_STATIC_LIBRARY", +}; + +androidndk.prebuiltKinds = { + [p.SHAREDLIB] = { + extension = ".so", + script = "PREBUILT_SHARED_LIBRARY", + }, + [p.STATICLIB] = { + extension = ".a", + script = "PREBUILT_STATIC_LIBRARY", + }, +}; + +function androidndk.isProjectSupported(project, reportWarnings) + -- Not allowing references or escaped $ in project names as they complicate path building as well as including project Android.mk files. + -- Premake treats any path beginning with $ as absolute. + -- LOCAL_MODULE and LOCAL_STATIC_LIBRARIES sweeping result: + -- Allowed directly on Linux: !+,-./@[]^_{}~ + -- Allowed if \-escaped on Linux: "` + -- Allowed if \-escaped on Linux, misleadingly displayed with \ in the terminal: &'()*<>? + -- Allowed if \\-escaped on Linux: \ + -- Disallowed: whitespaces, #$%:;=| + -- Stricter than LOCAL_MODULE_FILENAME derived from it by default for : and = (no action required). + -- However, more relaxed for / - to avoid issues, disallowing / too. + if string.find(project.name, "%$") ~= nil or androidndk.staticallyHasPatternPostEsc(p.esc(project.name), "[%s#%$%%/:;=|]") then + if reportWarnings then + p.warn( + "Skipping workspace '%s' project '%s' with name containing GNU make references, " .. + "whitespaces or characters disallowed in an ndk-build module name #$%%/:;=|", + project.workspace.name, project.name); + end + return false; + end + return true; +end + +function androidndk.getConfigCondition(buildcfg, platform, abi) + -- All requirements must be met by what is provided by ndk-build and the user for a configuration to be selected. + -- This is done by removing (filtering out) the current state from the set of the requirements. + -- If the resulting set is empty, the current state matches all the requirements. + + -- It's important that either all requirements are checked at once (like here), or the ABI check is the outermost ifeq. + -- PREMAKE_ANDROIDNDK_CONFIGURATIONS and _PLATFORMS include _all_ needed configurations and are the same throughout the ndk-build execution. + -- With nested buildcfg > platform > ABI conditionals, all ABIs would be entering the same buildcfg, and then the same platform within it. + -- However, the ABI is the primary criteria for selecting the platform (and the buildcfg if it depends on it). + -- The ABI should be narrowing down the selection either to one config, or to skipping the module. + -- The result for multiple configurations for one ABI is undefined. + + local state = {}; + local requirements = {}; + + -- Configs in projects are usually sorted by ABI (so ABI-agnostic fallbacks can be provided), then (by Premake) buildcfg, then platform. + -- So, using this order as well for better readability of generated makefiles. + + -- ABI filtering is done only when the ABI specified. + if abi ~= nil then + if abi ~= androidndk.ABI_ALL then + table.insert(state, "ABI=$(TARGET_ARCH_ABI)"); + table.insert(requirements, "ABI=" .. abi); + end + end + + -- At least one build configuration is required by Premake. + table.insert(state, "$(PREMAKE_ANDROIDNDK_CONFIGURATIONS_PREFIXED)"); + table.insert(requirements, "CONFIGURATION=" .. p.esc(buildcfg)); + + -- Platforms may be unspecified, in this case, configurations will have a nil platform. + if platform ~= nil then + table.insert(state, "$(PREMAKE_ANDROIDNDK_PLATFORMS_PREFIXED)"); + table.insert(requirements, "PLATFORM=" .. p.esc(platform)); + end + + return "ifeq ($(filter-out " .. table.concat(state, " ") .. "," .. table.concat(requirements, " ") .. "),)"; +end + +function androidndk.configUsesA32(config) + -- LOCAL_ARM_MODE and the .arm suffix have no effect on ABIs where they're not relevant - no need to check the ABI. + -- NDK uses T32 by default, and always switches to A32 for debug builds, but it's handled implicitly in it. + return config.armisa == "A32"; +end + +function androidndk.configUsesNeon(config) + -- LOCAL_ARM_NEON and the .neon suffix have no effect on ABIs where they're not relevant - no need to check the ABI. + -- On NDK r21, NEON is enabled by default. + -- Requiring setting vectorextensions to "ARMv7" (similar to "IA32") explicitly for disabling. + return config.vectorextensions ~= "ARMv7"; +end + +function androidndk.shellEscapePreQuotesPostEsc(argument, temporaryVariables) + if androidndk.hasNonEmptyMakeReferencesPostEsc(argument) then + -- Escape while building, after expanding variable and function references. + -- Can't use the text directly in a function call because it may contain brackets. + return "$(call " .. androidndk.shellEscapePreQuotesMakeCall .. "," .. androidndk.getTemporaryVariableReference(temporaryVariables, argument) .. ")"; + end + -- Unescape comment characters, which have \ in their escape sequence. + argument = string.gsub(argument, androidndk.commentUnescapePattern, "#"); + -- Pre-escape. + argument = string.gsub(string.gsub(argument, "\\", "\\\\"), "\"", "\\\""); + -- Re-escape comment characters. + argument = string.gsub(argument, "#", androidndk.commentEscapeString); + return argument; +end + +-- For calling with unescaped paths, otherwise paths starting with # will be considered absolute due to $()\# escaping. +-- In addition, may need to escape characters inserted by path.getrelative itself. +-- Unfortunately, if path.getrelative inserts any $ by itself while joining, they will be treated as references. +-- They wouldn't be able to be distinguished from references specified in the project itself. +function androidndk.getEscapedProjectRelativePath(project, file, joinWithLocalPath) + -- Must be synchronized with LOCAL_PATH setting. + -- Based on p.filename logic (relative to the location of the project's Android.mk). + -- If a path starts with a GNU make variable or function reference, it's assumed to be absolute, to allow using paths like $(NDK_ROOT). + -- Premake already treats paths starting with $ as absolute in path.getrelative, among other functions. + -- Relative paths starting with $ must be prefixed with ./. + local relativePath = path.getrelative(project.location or project.basedir, file); + -- For something like ./$(VARIABLE) (relative), path.getrelative can possibly return $(VARIABLE), which will be treated as absolute. + -- Premake treats all paths beginning with $ as absolute. + -- In this case, the path must still be written as relative to $(LOCAL_PATH), but path.join won't work. + -- path.join doesn't even work if the trailing part begins with ./$(VARIABLE). + -- Using concatenation for this purpose instead. + if joinWithLocalPath and (not path.isabsolute(relativePath) or (string.sub(relativePath, 1, 1) == "$" and string.sub(file, 1, 1) ~= "$")) then + relativePath = "$(LOCAL_PATH)/" .. relativePath; + end + return p.esc(relativePath); +end + +-- Excluding the .arm and .neon suffixes. +androidndk.asmExtension = ".asm"; +androidndk.cExtension = ".c"; +androidndk.sourceExtensions = { androidndk.asmExtension, androidndk.cExtension }; +androidndk.compilerFlagsVariables = { + [androidndk.asmExtension] = "ASMFLAGS", + [androidndk.cExtension] = "CONLYFLAGS", +}; +-- NDK r23 default-c++-extensions. +-- Strictly broader than path.iscppfile as of premake-core v5.0.0-alpha16. +-- path.iscppfile is not usable directly conveniently, however, as it also includes assembly and Objective-C/C++. +-- Do not use path.hasextension (or path.iscppfile) as it's case-insensitive unlike ndk-build, and will treat .C as C instead of C++. +-- Indexed for concatenation purposes. +androidndk.cppExtensions = { ".cc", ".cp", ".cxx", ".cpp", ".CPP", ".c++", ".C" }; +table.foreachi(androidndk.cppExtensions, function(extension) + -- Build a hash set for lookup in cppExtensions. + androidndk.cppExtensions[extension] = extension; + -- Register the extension as a C++ one. + table.insert(androidndk.sourceExtensions, extension); + androidndk.compilerFlagsVariables[extension] = "CPPFLAGS"; +end); +-- NDK r23 default-rs-extensions. +androidndk.rsExtensions = { ".rs", ".fs" }; +table.foreachi(androidndk.rsExtensions, function(extension) + -- Build a hash set for lookup in rsExtensions. + androidndk.rsExtensions[extension] = extension; + -- Register the extension as a RenderScript one. + table.insert(androidndk.sourceExtensions, extension); + androidndk.compilerFlagsVariables[extension] = "RENDERSCRIPT_FLAGS"; +end); +androidndk.asExtensions = { ".s", ".S" }; +table.foreachi(androidndk.asExtensions, function(extension) + -- Build a hash set for lookup in asExtensions. + androidndk.asExtensions[extension] = extension; + -- Register the extension as a GAS one. + table.insert(androidndk.sourceExtensions, extension); + androidndk.compilerFlagsVariables[extension] = "ASFLAGS"; +end); +-- Build a hash set for lookup in sourceExtensions. +table.foreachi(androidndk.sourceExtensions, function(extension) + androidndk.sourceExtensions[extension] = extension; +end); + +-- Define variables usable by projects for language filtering via extension filters. +-- Lowercase because it's the public interface for projects. +androidndk.filefilters = {}; +function androidndk.buildExtensionFileFilterTerms(extensions) + local extensionsCaseInsensitive = {}; + -- Premake filters are case-insensitive - don't add the same extension twice. + for i, extension in ipairs(extensions) do + table.insertkeyed(extensionsCaseInsensitive, string.lower(extension)); + end + -- Build filters including subdirectories, adding all possible ARM suffixes. + local extensionFilters = {}; + for i, extension in ipairs(extensionsCaseInsensitive) do + local extensionFilter = "**" .. extension; + table.insert(extensionFilters, extensionFilter); + table.insert(extensionFilters, extensionFilter .. ".arm"); + table.insert(extensionFilters, extensionFilter .. ".neon"); + table.insert(extensionFilters, extensionFilter .. ".arm.neon"); + end + return extensionFilters; +end +-- Initializing public helpers (and thus lowercase names) - don't remove even though they are not referenced within the module itself! +(function() + local languageFilterVariables = { + { + language = "as", + extensions = androidndk.asExtensions, + }, + { + language = "asm", + extensions = { androidndk.asmExtension }, + }, + { + language = "c", + extensions = { androidndk.cExtension }, + }, + { + language = "cpp", + -- Premake filters are case-insensitive, so C sources would pass **.C as well - skip it in the C++ filter. + -- It's not recommended to use the .C extension at all for this reason. + extensions = table.filter( + androidndk.cppExtensions, + function(extension) + return extension ~= ".C"; + end), + }, + { + language = "rs", + extensions = androidndk.rsExtensions, + }, + }; + for i, languageFilterVariable in ipairs(languageFilterVariables) do + local extensionsCaseInsensitive = {}; + -- Premake filters are case-insensitive - don't add the same extension twice. + for i2, extension in ipairs(languageFilterVariable.extensions) do + table.insertkeyed(extensionsCaseInsensitive, string.lower(extension)); + end + -- Build arrays of raw extensions with all possible ARM suffixes: + -- androidndk.prefixfilefilterextensions = { ".ext", ".ext.arm", ".ext.neon", ".ext.arm.neon"... } + -- This allows arbitrary usage of table.translate, including to prepend a specific file name. + local extensionsWithSuffixes = {}; + for i2, extension in ipairs(extensionsCaseInsensitive) do + table.insert(extensionsWithSuffixes, extension); + table.insert(extensionsWithSuffixes, extension .. ".arm"); + table.insert(extensionsWithSuffixes, extension .. ".neon"); + table.insert(extensionsWithSuffixes, extension .. ".arm.neon"); + end + androidndk.filefilters[languageFilterVariable.language .. "extensions"] = extensionsWithSuffixes; + -- Build inclusive filters for all subdirectories as a string. + -- androidndk.filefilters.language = "files:**.ext or files:**.ext.arm or files:**.ext.neon or files:**.ext.arm.neon..." + androidndk.filefilters[languageFilterVariable.language] = + table.concat( + table.translate( + extensionsWithSuffixes, + function(extension) + return "files:**" .. extension; + end), + " or "); + -- Build exclusive filters for all subdirectories as a table. + -- androidndk.filefilters.notlanguage = { "files:not **.ext", "files:not **.ext.arm", "files:not **.ext.neon", "files:not **.ext.arm.neon"... } + androidndk.filefilters["not" .. languageFilterVariable.language] = + table.translate( + extensionsWithSuffixes, + function(extension) + return "files:not **" .. extension; + end); + end +end)(); + +-- In this module, flags are gathered from both the project configuration and per-file configuration. +-- ndk-build, however, doesn't support per-file flags. +-- Per-file configuration is needed though because compilers for different languages may need different flags. +-- The `language` setting is a whole-project setting, it alone can't be used in a filter to specify which compiler should receive the flags. +-- Therefore, the file extension must be used as a filter. +-- This leads of a lot of duplication - every file in the project has project-level flags as well as extension-specific flags. +-- For this reason, each flag is added only once. +-- Because of this, a flag may contain one or multiple whitespace-separated compiler argument. +-- "-include a.h -include b.h -include \"c d.h\" -include \"c d.h\"" must NOT be deduplicated as: +-- { "-include", "a.h", "b.h", "\"c d.h\"" } +-- or as: +-- { "-include", "a.h", "b.h", "\"c", "d.h\"" } +-- It must be: +-- { "-include a.h", "-include b.h", "-include \"c d.h\"" } + +-- Built-in flags (derived from from boolean or enumeration settings) here are added to the flags table directly as p.esc(value). +-- Most shell characters in them will be escaped at build time, however, " and \ will not. +-- So, it's possible to have built-in flags corresponding to one (possibly with quoted whitespaces) or multiple (with unquoted whitespaces) compiler arguments. +-- If a double quote character or a backslash needs to be used as a raw character, it needs to be pre-escaped manually in the flag literal here. +-- GNU make variable and function references should be used in built-in flags very carefully for the same reason. +-- Ordering of the flags is preserved only for their first occurrences in each of the LOCAL_languageFLAGS. +-- In Clang, if there are conflicting flags (such as -f and -fno), the ones later in the list take precedence. +-- Per-extension flags are therefore added after project-scope flags so the former can override the latter. + +-- Makes the value a single command line arguments passed to the compiler with a prefix, which may be in the same or in a separate argument. +-- The value must be pre-escaped with p.esc. +-- The prefix (not pre-escaped) will be handled similar to built-in flags, and may contain unquoted whitespaces for separate command line arguments. +-- Examples of the output: +-- * -DCSTRING=\"Hello\" +-- * -D"CSTRING=\"Hello, world!\"" +-- * -D "YASMSTRING=\"Hello, world!\"" +-- * -include header.h +-- * -include "header file.h" +-- * PREMAKE_ANDROIDNDK_TEMPORARY_1 := $(ENVIRONMENT_VARIABLE)/New folder (2)/header file.h +-- -include "$(call PREMAKE_ANDROIDNDK_SHELL_ESCAPE_PRE_QUOTES,$(PREMAKE_ANDROIDNDK_TEMPORARY_1))" +function androidndk.getSingleArgumentPrefixedFlagPostValueEsc(value, prefix, temporaryVariables) + local flagParts = { p.esc(prefix) }; + -- While it's completely safe to add excessive quotes in general, in some cases ndk-build doesn't expect them. + -- One example being LOCAL_LDLIBS, which is designed only for linking to Android system libraries. + -- ndk-build checks each entry against a list of known Android system libraries, and throws a warning if any non-system ones are specified. + -- The intended usage pattern of LOCAL_LDLIBS `LOCAL_LDLIBS := -landroid -llog`, not ``LOCAL_LDLIBS := -l"android" -l"log"`. + -- So, adding quotes only if there are whitespaces or references (for which it's not known whether they will be expanded to something containing whitespaces). + local isQuoted = androidndk.hasNonEmptyMakeReferencesPostEsc(value) or androidndk.staticallyHasPatternPostEsc(value, "%s"); + if isQuoted then + table.insert(flagParts, "\""); + end + table.insert(flagParts, androidndk.shellEscapePreQuotesPostEsc(value, temporaryVariables)); + if isQuoted then + table.insert(flagParts, "\""); + end + return table.concat(flagParts); +end + +-- System include directories are normally placed after the local ones, including in ndk-build itself. +androidndk.includeDirsConfigSettings = { "includedirs", "sysincludedirs" }; + +function androidndk.addIncludeDirs(includeDirs, config) + -- In LOCAL_C_INCLUDES (tested on it, but LOCAL_RENDERSCRIPT_INCLUDES goes to the host-c-includes call too): + -- Allowed directly on Linux: !%*+,-./:=?@[]^_{}~ + -- Allowed if \-escaped on Linux: "&'();<>\`| + -- Escaped UNC paths (beginning with \\\\) are supported on Windows. + -- Disallowed: whitespaces, #$ + -- In the NDK itself, either absolute or relative to the NDK root, not to the local path - need $(LOCAL_PATH) explicitly. + for i, settingName in ipairs(androidndk.includeDirsConfigSettings) do + local settingOutput = includeDirs[settingName]; + if settingOutput == nil then + settingOutput = {}; + includeDirs[settingName] = settingOutput; + end + local settingInput = config[settingName]; + for i2, includeDir in ipairs(settingInput) do + local includeDirPath = androidndk.getEscapedProjectRelativePath(config.project, includeDir, true); + if androidndk.staticallyHasPatternPostEsc(includeDirPath, "[%s#%$]") then + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' include directory '%s' with whitespaces or disallowed characters #$", + config.workspace.name, config.project.name, config.name, includeDir); + else + table.insert(settingOutput, includeDirPath); + end + end + end +end + +function androidndk.writeProjectIncludeDirs(includeDirsForAllVariables, variable, escapeShellCharacters) + local includeDirsForVariable = includeDirsForAllVariables[variable]; + if includeDirsForVariable == nil then + return false; + end + -- Merge all config settings the include directories are gathered from in the correct order. + local includeDirsForVariableMerged = {}; + for i, configSettingName in ipairs(androidndk.includeDirsConfigSettings) do + if includeDirsForVariable[configSettingName] ~= nil then + for i2, includeDir in ipairs(includeDirsForVariable[configSettingName]) do + table.insertkeyed(includeDirsForVariableMerged, includeDir); + end + end + end + local localVariable = "LOCAL_" .. variable; + if androidndk.assignToVariablePostEsc(localVariable, includeDirsForVariableMerged, true, androidndk.shellEscapeChecker) then + if escapeShellCharacters then + androidndk.writePostProcessCall(localVariable, androidndk.shellEscapeMakeCall); + end + return true; + end + return false; +end + +-- Assuming Clang - GCC was removed in NDK r13. +-- RenderScript is based on C99, but for writing compute kernels. + +-- Supported values are built-in flags as single strings and tables of strings (will be separate flags table elements). + +androidndk.asmFlagsSettingFlags = { + { key = "FatalCompileWarnings", value = "-Werror" }, +}; + +androidndk.cCppFlagsSettingFlags = { + { key = "FatalCompileWarnings", value = "-Werror" }, + -- LinkTimeOptimization is per-project, not per-extension, as it needs to be easily queryable in static library dependency analysis. + -- NoBufferSecurityCheck is C++-only in p.clang, but nothing precludes its usage in C. + -- Android uses -fstack-protector-strong by default as of NDK r23. + { key = "NoBufferSecurityCheck", value = "-fno-stack-protector" }, + { key = "ShadowedVariables", value = "-Wshadow" }, + { key = "UndefinedIdentifiers", value = "-Wundef" }, +}; + +androidndk.rsFlagsSettingFlags = { + { key = "FatalCompileWarnings", value = "-Werror" }, + { key = "ShadowedVariables", value = "-Wshadow" }, + { key = "UndefinedIdentifiers", value = "-Wundef" }, +}; + +-- Instead of using p.clang and mapFlags (with all its complexity), listing only what's relevant to ndk-build. +-- Supported keys are only strings and booleans (as p.OFF and p.ON), no tables (to avoid the complexity of keeping a deterministic order). +-- Supported values are built-in flags as single strings and tables of strings (will be separate flags table elements). +-- Values may contain unquoted (for multiple options) or quoted whitespaces, or \-escaped " and \, but p.esc will be done before writing. + +androidndk.asmSettingValueFlags = { + { + key = "warnings", + values = { + [p.OFF] = "-w", + }, + }, +}; + +androidndk.cCppSettingValueFlags = { + -- architecture is chosen via APP_ABI. + -- flags are handled separately to avoid ensuring deterministic table ordering. + { + key = "floatingpoint", + values = { + Fast = "-ffast-math", + }, + }, + -- floatingpointexceptions not present in p.clang as of v5.0.0-alpha16, added here. + { + key = "floatingpointexceptions", + values = { + [p.ON] = "-ftrapping-math", + [p.OFF] = "-fno-trapping-math", + }, + }, + { + key = "strictaliasing", + values = { + [p.OFF] = "-fno-strict-aliasing", + Level1 = { "-fstrict-aliasing", "-Wstrict-aliasing=1" }, + Level2 = { "-fstrict-aliasing", "-Wstrict-aliasing=2" }, + Level3 = { "-fstrict-aliasing", "-Wstrict-aliasing=3" }, + }, + }, + -- optimize is chosen via APP_OPTIM. + -- pic is always enabled by ndk-build and is required on Android. + -- vectorextensions are per-architecture. + -- isaextensions are per-architecture. + { + key = "warnings", + values = { + [p.OFF] = "-w", + High = "-Wall", + Extra = { "-Wall", "-Wextra" }, + Everything = "-Weverything", + }, + }, + -- symbols are chosen via APP_DEBUG. + { + key = "unsignedchar", + values = { + [p.ON] = "-funsigned-char", + [p.OFF] = "-fno-unsigned-char", + }, + }, + -- omitframepointer Off is needed for the Address Sanitizer, so exposed here. + -- More info about omitframepointer on Android: https://github.com/android/ndk/issues/824 + { + key = "omitframepointer", + values = { + [p.ON] = "-fomit-frame-pointer", + [p.OFF] = "-fno-omit-frame-pointer", + }, + }, + -- visibility and inlinesvisibility are C++-only in p.clang in v5.0.0-alpha16, but nothing precludes their usage in C. + { + key = "visibility", + values = { + Default = "-fvisibility=default", + Hidden = "-fvisibility=hidden", + Internal = "-fvisibility=internal", + Protected = "-fvisibility=protected", + }, + }, + { + key = "inlinesvisibility", + values = { + Hidden = "-fvisibility-inlines-hidden", + }, + }, +}; + +androidndk.cSettingValueFlags = { + { + key = "cdialect", + values = { + ["C89"] = "-std=c89", + ["C90"] = "-std=c90", + ["C99"] = "-std=c99", + ["C11"] = "-std=c11", + ["gnu89"] = "-std=gnu89", + ["gnu90"] = "-std=gnu90", + ["gnu99"] = "-std=gnu99", + ["gnu11"] = "-std=gnu11", + }, + }, +} + +androidndk.cppSettingValueFlags = { + -- exceptionhandling is chosen via LOCAL_CPP_FEATURES. + { + key = "cppdialect", + values = { + ["C++98"] = "-std=c++98", + ["C++0x"] = "-std=c++0x", + ["C++11"] = "-std=c++11", + ["C++1y"] = "-std=c++1y", + ["C++14"] = "-std=c++14", + ["C++1z"] = "-std=c++1z", + ["C++17"] = "-std=c++17", + ["C++2a"] = "-std=c++2a", + ["C++20"] = "-std=c++20", + ["gnu++98"] = "-std=gnu++98", + ["gnu++0x"] = "-std=gnu++0x", + ["gnu++11"] = "-std=gnu++11", + ["gnu++1y"] = "-std=gnu++1y", + ["gnu++14"] = "-std=gnu++14", + ["gnu++1z"] = "-std=gnu++1z", + ["gnu++17"] = "-std=gnu++17", + ["gnu++2a"] = "-std=gnu++2a", + ["gnu++20"] = "-std=gnu++20", + ["C++latest"] = "-std=c++20", + }, + }, + -- rtti is chosen via LOCAL_CPP_FEATURES. +}; + +androidndk.rsSettingValueFlags = { + { + key = "warnings", + values = { + [p.OFF] = "-w", + High = "-Wall", + Extra = { "-Wall", "-Wextra" }, + Everything = "-Weverything", + }, + }, + -- No other cCppFlagsSettingFlags and cSettingValueFlags are supported. +}; + +androidndk.asmWarningFlags = { + { key = "enablewarnings", prefix = "-W" }, + { key = "disablewarnings", prefix = "-Wno-" }, + -- No -Werror= on YASM, pick the closest to the intention. + { key = "fatalwarnings", prefix = "-W" }, +}; + +androidndk.cCppWarningFlags = { + { key = "enablewarnings", prefix = "-W" }, + { key = "disablewarnings", prefix = "-Wno-" }, + { key = "fatalwarnings", prefix = "-Werror=" }, +}; + +function androidndk.addCompilerFlagsForExtension(flagsForExtension, extension, config, temporaryVariables) + -- Make sure the table for generic flags exists. + if flagsForExtension.buildoptions == nil then + flagsForExtension.buildoptions = {}; + end + + local isCCpp = extension == androidndk.cExtension or androidndk.cppExtensions[extension] ~= nil; + + local languageDefinesPrefix = nil; + local languageUndefinesPrefix = nil; + local languageFlagsSettingFlags = nil; + local languageSettingValueFlagsTables = {}; + local languageWarningFlags = nil; + if extension == androidndk.asmExtension then + -- The defines prefix is separated into a different argument. + languageDefinesPrefix = "-D "; + languageUndefinesPrefix = "-U "; + languageFlagsSettingFlags = androidndk.asmFlagsSettingFlags; + table.insert(languageSettingValueFlagsTables, androidndk.asmSettingValueFlags); + languageWarningFlags = androidndk.asmWarningFlags; + elseif isCCpp then + -- The defines prefix is in the same argument (though separation also works, but ndk-build itself doesn't separate). + languageDefinesPrefix = "-D"; + languageUndefinesPrefix = "-U"; + -- Currently only shared between C and C++. + languageFlagsSettingFlags = androidndk.cCppFlagsSettingFlags; + table.insert(languageSettingValueFlagsTables, androidndk.cCppSettingValueFlags); + if androidndk.cppExtensions[extension] ~= nil then + table.insert(languageSettingValueFlagsTables, androidndk.cppSettingValueFlags); + else + table.insert(languageSettingValueFlagsTables, androidndk.cSettingValueFlags); + end + languageWarningFlags = androidndk.cCppWarningFlags; + elseif androidndk.rsExtensions[extension] ~= nil then + -- -D and -U are not supported. + languageFlagsSettingFlags = androidndk.rsFlagsSettingFlags; + table.insert(languageSettingValueFlagsTables, androidndk.rsSettingValueFlags); + languageWarningFlags = androidndk.cCppWarningFlags; + end + + -- Preprocessor definitions (not supported by the GNU assembler). + -- Potentially with whitespaces, but need to go to a single (quoted) argument. + -- Defines before undefines for consistency with other Premake actions such as Visual Studio. + -- Placing them in separate tables so they're not mixed if both defines are undefines are provided by both the project and the extension filter. + -- Not doing the same for -D and -U provided via raw buildoptions though to avoid interfering with the project-provided order. + if languageDefinesPrefix ~= nil then + if flagsForExtension.defines == nil then + flagsForExtension.defines = {}; + end + for i, define in ipairs(config.defines) do + table.insertkeyed( + flagsForExtension.defines, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(p.esc(define), languageDefinesPrefix, temporaryVariables)); + end + end + if languageUndefinesPrefix ~= nil then + if flagsForExtension.undefines == nil then + flagsForExtension.undefines = {}; + end + for i, undefine in ipairs(config.undefines) do + table.insertkeyed( + flagsForExtension.undefines, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(p.esc(undefine), languageUndefinesPrefix, temporaryVariables)); + end + end + + -- Forced include paths (not supported by the GNU assembler). + if extension == androidndk.asmExtension then + for i, forceInclude in ipairs(config.forceincludes) do + local forceIncludePath = androidndk.getEscapedProjectRelativePath(config.project, forceInclude, true); + if androidndk.staticallyHasPatternPostEsc(forceIncludePath, "[\"';]") then + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' Yasm force include '%s' with path containing disallowed characters \"';", + config.workspace.name, config.project.name, config.name, forceInclude); + else + table.insertkeyed( + flagsForExtension.buildoptions, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(forceIncludePath, "-P ", temporaryVariables)); + end + end + elseif isCCpp then + -- -include is not supported by RenderScript. + for i, forceInclude in ipairs(config.forceincludes) do + local forceIncludePath = androidndk.getEscapedProjectRelativePath(config.project, forceInclude, true); + -- -include %s becomes #include "%s", the inner " is considered a string terminator. + if androidndk.staticallyHasPatternPostEsc(forceIncludePath, "[\"]") then + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' C/C++ force include '%s' with path containing a disallowed double quote character", + config.workspace.name, config.project.name, config.name, forceInclude); + else + table.insertkeyed( + flagsForExtension.buildoptions, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(forceIncludePath, "-include ", temporaryVariables)); + end + end + end + + -- Flags for configuration flags setting. + if languageFlagsSettingFlags ~= nil then + for i, flagsSettingKeyValue in ipairs(languageFlagsSettingFlags) do + if config.flags[flagsSettingKeyValue.key] then + local flagsSettingValue = flagsSettingKeyValue.value; + if type(flagsSettingValue) ~= "table" then + flagsSettingValue = { flagsSettingValue }; + end + for i2, flagsSettingValueBuildOption in ipairs(flagsSettingValue) do + table.insertkeyed(flagsForExtension.buildoptions, p.esc(flagsSettingValueBuildOption)); + end + end + end + end + + -- Flags for setting values. + for i, languageSettingValueFlags in ipairs(languageSettingValueFlagsTables) do + for i2, languageSettingValueKeyValues in ipairs(languageSettingValueFlags) do + local settingValue = config[languageSettingValueKeyValues.key]; + if settingValue ~= nil then + -- Convert boolean to off/on. + if settingValue == false then + settingValue = p.OFF; + elseif settingValue == true then + settingValue = p.ON; + end + local settingValueFlags = languageSettingValueKeyValues.values[settingValue]; + if settingValueFlags ~= nil then + if type(settingValueFlags) ~= "table" then + settingValueFlags = { settingValueFlags }; + end + for i3, settingValueFlag in ipairs(settingValueFlags) do + table.insertkeyed(flagsForExtension.buildoptions, p.esc(settingValueFlag)); + end + end + end + end + end + + -- Individual warnings. + -- After generic setting flags which include the default warning behavior so -Wname can override -w. + if languageWarningFlags ~= nil then + for i, warningKeyPrefix in ipairs(languageWarningFlags) do + for i2, warning in ipairs(config[warningKeyPrefix.key]) do + table.insertkeyed( + flagsForExtension.buildoptions, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(p.esc(warning), warningKeyPrefix.prefix, temporaryVariables)); + end + end + end + + -- Add raw project-specified flags, as single or multiple arguments, after everything (so they can cancel internal flags). + -- Quoting, or escaping of quotes, must be done by the project instead, as a single build option may contain multiple arguments. + -- Build options are platform-dependent anyway - the module is free to establish any rules. + for i, buildOption in ipairs(config.buildoptions) do + local buildOptionEscaped = p.esc(buildOption); + table.insertkeyed(flagsForExtension.buildoptions, buildOptionEscaped); + if isCCpp and androidndk.isFltoOption(buildOptionEscaped) then + -- Store the last (thus the highest-priority) -flto type used by the language. + -- RenderScript doesn't support -flto. + flagsForExtension.flto = buildOptionEscaped; + end + end +end + +-- Defines before undefines for consistency with other Premake actions such as Visual Studio. +-- Everything special before everything generic (including raw project buildoptions) for the same reason. +-- Separate to make sure -D -U -D -U instead of -D -D -U -U doesn't happen if both are provided by both the project and the extension filter. +androidndk.projectCompilerFlagsOrder = { "defines", "undefines", "buildoptions" }; + +function androidndk.writeProjectCompilerFlags(flagsForAllVariables, variable, escapeShellCharacters) + local flagsForVariable = flagsForAllVariables[variable]; + if flagsForVariable == nil then + return false; + end + local flagsForVariableMerged = {}; + -- Merge all sources of flags in the correct order. + for i, flagsTableName in ipairs(androidndk.projectCompilerFlagsOrder) do + if flagsForVariable[flagsTableName] ~= nil then + for i2, flag in ipairs(flagsForVariable[flagsTableName]) do + table.insertkeyed(flagsForVariableMerged, flag); + end + end + end + local localVariable = "LOCAL_" .. variable; + if androidndk.assignToVariablePostEsc(localVariable, flagsForVariableMerged, true, androidndk.shellEscapePostQuotesChecker) then + if escapeShellCharacters then + androidndk.writePostProcessCall(localVariable, androidndk.shellEscapePostQuotesMakeCall); + end + return true; + end + return false; +end + +-- Built-in flags for ISA and vector extensions. + +androidndk.abiIsaExtensions = { + ["x86"] = { + MOVBE = "-mmovbe", + POPCNT = "-mpopcnt", + PCLMUL = "-mpclmul", + LZCNT = "-mlzcnt", + BMI = "-mbmi", + BMI2 = "-mbmi2", + F16C = "-mf16c", + AES = "-maes", + FMA = "-mfma", + FMA4 = "-mfma4", + RDRND = "-mrdrnd", + }, +}; +androidndk.abiIsaExtensions["x86_64"] = androidndk.abiIsaExtensions["x86"]; + +androidndk.abiVectorExtensions = { + ["x86"] = { + -- Up to SSSE3 are required. + ["SSE4.1"] = "-msse4.1", + ["SSE4.2"] = "-msse4.2", + ["AVX"] = "-mavx", + ["AVX2"] = "-mavx2", + }, + ["x86_64"] = { + -- Up to SSE4.2 are required. + ["AVX"] = "-mavx", + ["AVX2"] = "-mavx2", + }, +}; + +-- Escaped or not doesn't matter. +function androidndk.isFltoOption(option) + -- Remove quotes (so "-flto..." is handled like -flto...). + -- Escaped or not doesn't matter as the pattern being checked doesn't include a backslash. + option = string.gsub(option, "\"", ""); + -- Let -flto alone, or -flto=..., pass. + return string.find(option, "^%s*%-flto%s$") ~= nil or string.find(option, "^%s*%-flto=") ~= nil; +end + +-- ldFlagsUndefined receives -u and --undefined from `linkoptions`. +-- ldLibsDirFlags receives -L from `linkoptions` and -L for `links`. +-- ldLibsSysDirFlags receives -L for `syslibdirs`. +-- ldLibsLibFlags receives -l from `linkoptions` and -l for `links`. +-- ldFlags receives the rest of `linkoptions`. +-- Any output table can be nil. +-- In this case, only libraries to link to will be obtained and written to ldLibs*Flags. +-- Returns the last encountered (thus highest-priority) -flto option if any is passed (so non-default LTO types can be used with transitivity). +function androidndk.addSingleConfigLinkerFlags(config, ldFlagsUndefined, ldFlags, ldLibsDirFlags, ldLibsSysDirFlags, ldLibsLibFlags, temporaryVariables) + -- NDK passes LDFLAGS before LDLIBS. + -- It also placed local LDLIBS before imported LDLIBS. + -- Entries for the system libraries (`syslibdirs` here specifically) should be the last according to this ordering also. + -- The system can be considered the deepest import of everything. + -- For LDFLAGS, it's the reverse, however - imported LDFLAGS are before local LDFLAGS. + + -- This module also distributes `linkoptions` between LDFLAGS and LDLIBS instead of passing all to LDFLAGS. + -- LDLIBS is what's designed to hold the -l flags for the system libraries to link to. + -- While LDFLAGS can contain -l too, it's not the intended way of passing them. + -- The reason why this is taken into consideration, and `linkoptions` are parsed at all, is the special case of name collision resolution. + -- A workspace may contain its own project, for example, named "log", and in this case a "log" link would be considered a sibling link, not a system one. + -- An alternative would be to link to ":liblog.so". + -- However, this will result in a warning as ndk-build uses simpler logic for checking whether only system libraries are specified which fails for that. + -- Therefore, this module offers a way to pass "-llog" via `linkoptions`. + + -- Normally though -l should be used only for a small fixed set of Android NDK system libraries. + -- All this logic is therefore a massive overkill. + -- Prebuilt library projects normally should be used instead of -L and -l. + -- However, for the ease of porting, and to avoid adding constraints, letting projects link directly to libraries via raw linker arguments anyway. + -- Specifying anything but -l would throw a warning though. + + -- Search paths (-L) apply to all -l options regardless of the order. + -- They can be written to either LDFLAGS or LDLIBS. + -- This module implements dependency transitivity a custom way, stopping iterating at shared libraries (unlike NDK's internal exports). + -- For this purpose, it only imports everything that goes to LDLIBS, but not to LDFLAGS - assuming it contains everything needed for library linkage. + -- So, always writing search paths to LDLIBS here, not to LDFLAGS. + + -- Handling `linkoptions` before `links` and `syslibdirs` because the latter is conceptually closer to LDFLAGS, and the latter two to LDLIBS. + -- Considering -L specified in `linkoptions` as the more generic `links` (which are placed before `syslibdirs`) for the same reason as well. + + local flto = nil; + local longLinkOptions = { + { + name = "--library", + destination = ldLibsLibFlags, + }, + { + name = "--library-path", + destination = ldLibsDirFlags, + }, + { + name = "--undefined", + destination = ldFlagsUndefined, + }, + }; + for i, linkOptionUnescaped in ipairs(config.linkoptions) do + local linkOption = p.esc(linkOptionUnescaped); + -- Check which output table should receive the option. + local linkOptionDestination = ldFlags; + -- -flto is a Clang++ option, not an LLD one - don't check it if there's -Wl. + -- Also, it's either a full option, or an option with a value - check it in a special way for this reason as well. + if androidndk.isFltoOption(linkOption) then + flto = linkOption; + else + -- Trim whitespaces (until the first quote if present). + local linkOptionFirstNonWhitespace = string.find(linkOption, "[%S]"); + if linkOptionFirstNonWhitespace ~= nil then + -- Remove quotes (so "-l..." is handled like -l...). + -- Escaped or not doesn't matter as none of the checked prefixes include a backslash. + local linkOptionForPrefix = string.gsub(string.sub(linkOption, linkOptionFirstNonWhitespace, -1), "\"", ""); + -- ndk-build passes the flags to Clang++, not to LLD directly. + -- Therefore, most of linker options must be passed via -Wl, and for those that don't, the presence of -Wl doesn't matter. + -- Specifically, -l and -L can be passed to both Clang++ and LLD, but --library and --library-path can't (and require -Wl). + -- -l and -L can be either attached directly to the name or placed in a separate (whitespace-separated) argument. + -- --library and --library-path can be either attached with a = or placed in a separate argument. + -- Since --library and --library-path require -Wl, arguments are comma-separate rather than whitespace-separated. + local wlPrefix = "-Wl,"; + local wlPrefixLength = string.len(wlPrefix); + local hasWl = string.sub(linkOption, 1, string.len(wlPrefixLength)) == wlPrefix; + if hasWl then + linkOptionForPrefix = string.sub(linkOptionForPrefix, wlPrefixLength + 1, -1); + end + local linkOptionSingleCharacterPrefix = string.sub(linkOptionForPrefix, 1, 2); + if linkOptionSingleCharacterPrefix == "-u" then + linkOptionDestination = ldFlagsUndefined; + elseif linkOptionSingleCharacterPrefix == "-l" then + linkOptionDestination = ldLibsLibFlags; + elseif linkOptionSingleCharacterPrefix == "-L" then + linkOptionDestination = ldLibsDirFlags; + else + if hasWl then + -- Check if --Wl,--option=value or --Wl,--option,value is used. + for i2, longLinkOption in ipairs(longLinkOptions) do + local longLinkOptionPrefixSub = string.sub(linkOptionForPrefix, 1, string.len(longLinkOption.name)); + if longLinkOption.name == (longLinkOptionPrefixSub .. "=") or longLinkOption.name == (longLinkOptionPrefixSub .. ",") then + linkOptionDestination = longLinkOption.destination; + break; + end + end + end + end + end + end + -- Add the flag to the needed table if adding to one was requested at all. + -- Quoting, or escaping of quotes, must be done by the project instead, as a single link option may contain multiple arguments. + -- Link options are platform-dependent anyway - the module is free to establish any rules. + if linkOptionDestination ~= nil then + table.insertkeyed(linkOptionDestination, linkOption); + end + end + + -- Local search paths (`libdirs` and `links`). + if ldLibsDirFlags ~= nil then + for i, libDir in ipairs(p.config.getlinks(config, "system", "directory")) do + local libDirRelativePath = androidndk.getEscapedProjectRelativePath(config.project, libDir, true); + table.insertkeyed( + ldLibsDirFlags, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(libDirRelativePath, "-L", temporaryVariables)); + end + end + + -- System search paths. + if ldLibsSysDirFlags ~= nil then + for i, sysLibDir in ipairs(config.syslibdirs) do + local sysLibDirRelativePath = androidndk.getEscapedProjectRelativePath(config.project, sysLibDir, true); + table.insertkeyed( + ldLibsSysDirFlags, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(sysLibDirRelativePath, "-L", temporaryVariables)); + end + end + + -- Local links. + -- Specifying the : prefix directly in `links` is allowed to force treatment of the name as a full file name instead of a short name. + -- However, just specifying the full file name is also allowed for simplicity of the usage (especially of the usage of full paths). + -- libname.so will be treated as libname.so, not as liblibname.so.a or liblibname.so.so. + -- However, something like name.thumb will be treated as libname.thumb.a/libname.thumb.so still. + -- Making no special assumptions about GNU make references (such as environment variables). + -- The intended usage of LOCAL_LDLIBS is purely for linking to the libraries provided by the Android NDK via their short names anyway. + -- To use, for instance, a full path from an environment variable (or a different kind of a GNU make reference), specify: + -- links({ "$(dir $(VARIABLE))/:$(notdir $(VARIABLE))" }) + if ldLibsLibFlags ~= nil then + for i, libName in ipairs(p.config.getlinks(config, "system", "name")) do + local libNamePrefix = ""; + if string.sub(libName, 1, 1) ~= ":" then + -- Full name not forced. + -- Check if using the full name anyway. + local libExtension = path.getextension(libName); + if libExtension == ".a" or libExtension == ".so" then + libNamePrefix = ":"; + end + end + table.insertkeyed( + ldLibsLibFlags, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(p.esc(libNamePrefix .. libName), "-l", temporaryVariables)); + end + end + + return flto; +end + +function androidndk.generateProjectConfig(config) + local temporaryVariables = {}; + + local configAbi = androidndk.getConfigAbi(config); + assert(configAbi ~= nil, "Configurations with invalid ABIs must be skipped in isConfigSupported"); + + local configUsesA32 = androidndk.configUsesA32(config); + local configUsesNeon = androidndk.configUsesNeon(config); + + -- Get the precompiler header path. + -- Handling of the ARM instruction set and NEON is done by ndk-build implicitly, not possible and not needed to specify suffixes. + -- All the inclusion of the built header is done by ndk-build itself implicitly. + -- At this point, it's needed for getting the file configuration for it so, if needed, C++-specific options can be extracted from it. + local pch = nil; + if not config.flags.NoPCH and config.pchheader ~= nil then + pch = androidndk.getEscapedProjectRelativePath(config.project, config.pchheader, false); + -- LOCAL_PCH is involved a value that goes to LOCAL_SRC_FILES internally in ndk-build, so the rules are the same. + -- However, = is also not allowed. + if androidndk.staticallyHasPatternPostEsc(pch, "[%s\"#%$&'%(%),;<=>`|]") then + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' precompiled header '%s' " .. + "with path containing whitespaces or disallowed characters \"#$&'(),;<=>`|", + config.workspace.name, config.project.name, config.name, pch); + pch = nil; + end + end + local pchFileConfig = nil; + + -- Gather source files not excluded from build. + local sourceTree = p.project.getsourcetree(config.project); + local configAbiSupportsYasm = configAbi == "x86" or configAbi == "x86_64" or configAbi == androidndk.ABI_ALL; + -- External Android.mk files and prebuilt libraries. + local sourcePrecreatedFiles = {}; + -- Keys - sequential indexes, paths without .arm and .neon suffixes. + -- Values - paths with .arm and .neon suffixes. + local sourceFiles = {}; + local sourceFilesYasmConditional = {}; + -- includeDirs[variable] and compilerFlags[variable] may be nil if no source files contributing to them have been found yet. + -- Each includeDirs element optionally contains includedirs = {}, sysincludedirs = {}. + local includeDirs = {}; + local renderScriptIncludeDirsVariable = iif(config.renderscriptincludedirsoverride == true, "RENDERSCRIPT_INCLUDES_OVERRIDE", "RENDERSCRIPT_INCLUDES"); + -- Each compilerFlags element optionally contains defines = {}, undefines = {}, buildoptions (used for the rest of flags) = {}, flto = "...". + -- flto, if present, contains the last -flto flag encountered for this extension. + local compilerFlags = {}; + -- For simplicity, for project-defined flags, using separate CONLYFLAGS and CPPFLAGS. + -- Using LOCAL_CFLAGS is also undesirable because they go not only to C and C++, but to GNU assembly as well. + -- See the definition of ev-compile-s-source in ndk-build. + local configIncludeDirsAdded = {}; + local configCompilerFlagsAdded = {}; + p.tree.traverse(sourceTree, { + onleaf = function(fileNode, depth) + local fileConfig = p.fileconfig.getconfig(fileNode, config); + -- Check if the file is present at all in the current configuration and not explicitly excluded with a file filter. + if fileConfig ~= nil then + -- abspath must be used, as with relpath or path, a path relative to the Android.mk location is written in the end, not to LOCAL_PATH. + local sourceOriginalRelativePath = androidndk.getEscapedProjectRelativePath(config.project, fileNode.abspath, false); + -- If this is the precompiled header, store the file configuration for it so it can be used later to obtain C++-specific settings. + -- ExcludeFromBuild has no effect on whether the precompiled header is used in other Premake actions. + if sourceOriginalRelativePath == pch then + pchFileConfig = fileConfig; + end + if not fileConfig.flags.ExcludeFromBuild then + -- Handle according to the extension. + -- Supporting external Android.mk files, prebuilt libraries, source files (possibly with .arm and .neon suffixes). + -- Unlike path.hasextension, ndk-build is case-sensitive (.C is treated as C++ by ndk-build by default). + -- Therefore, using == for path.getextension instead of path.hasextension as the latter is case-insensitive. + local sourceOriginalExtension = path.getextension(sourceOriginalRelativePath); + local sourceOriginalBaseName = path.getbasename(sourceOriginalRelativePath); + -- Check if the file name is not empty. + if sourceOriginalBaseName ~= "" then + if sourceOriginalExtension == ".mk" then + -- Need the full path, not a relative path like in LOCAL_SRC_FILES. + local sourceAndroidMkPath = androidndk.getEscapedProjectRelativePath(config.project, fileNode.abspath, true); + -- Whitespaces in include statements are supported, but need to be escaped with \ which themselves are escapable in this case. + -- Meaning, n/2 backslashes will be passed, and escaping will be done only by an odd number of backslashes. + -- This makes it hard to handle cases of backslashes before whitespaces in the original path. + -- Backslashes not before spaces are treated as raw characters, however - can't replace all with two backslashes. + if androidndk.staticallyHasPatternPostEsc(sourceAndroidMkPath, "[%s]") then + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' file '%s' " .. + "with path containing whitespaces, which are not supported", + config.workspace.name, config.project.name, config.name, sourceAndroidMkPath); + else + table.insertkeyed(sourcePrecreatedFiles, sourceAndroidMkPath); + end + elseif sourceOriginalExtension == ".a" or sourceOriginalExtension == ".so" then + -- LOCAL_SRC_FILES limitations for prebuilt libraries: + -- Backslash escaping of shell characters is NOT supported on Windows. + -- Escaping with a backslash works on Linux, however, but it's not needed for any of the allowed characters there. + -- On Linux, the path goes to `cp`. + -- Disallowed characters on Linux: whitespaces, "#$%&'()*:;<>?\`| + -- * is treated as a wildcard, so it can't truly be a part of a file name either. + -- \ is allowed on Windows, however, as a path separator. + -- On Windows, the path goes to `copy` after replacing / with \. + -- Disallowed characters on Windows: whitespaces, "#$%&*;<>?| + -- : is allowed on Windows as a drive letter separator. + -- Therefore, on Windows, strictly more different characters are allowed. + -- The user may, however, possibly want to use the generated scripts on just Windows. + -- For this reason, only paths with characters disallowed on both (not either) hosts must be filtered out here. + -- There's no need to impose overly strict constraints, this check acts as a portability aid. + if androidndk.staticallyHasPatternPostEsc(sourceOriginalRelativePath, "[%s\"#%$%%&%*;<>%?|]") then + -- % needs to be escaped as %% in printf format strings. + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' file '%s' " .. + "with path containing whitespaces or disallowed characters \"#$%%&*;<>?|", + config.workspace.name, config.project.name, config.name, sourceOriginalRelativePath); + else + table.insertkeyed(sourcePrecreatedFiles, sourceOriginalRelativePath); + end + else + -- .arm and .neon suffixes internally enable the A32 instruction set and NEON respectively for the base file. + -- They're also supported on ABIs other than armeabi-v7a, having no effect in this case, the file is built as usual. + -- With LOCAL_ARM_MODE := arm and LOCAL_ARM_NEON := true respectively, they have no effect also. + -- These suffixes are allowed for all types of source files, even x86 assembly. + local sourceRelativePath = sourceOriginalRelativePath; + local sourceExtension = sourceOriginalExtension; + -- Only .arm, .neon and .arm.neon combinations are supported - not .neon.arm. + local sourceHasNeonForced = sourceExtension == ".neon"; + if sourceHasNeonForced then + sourceRelativePath = path.removeextension(sourceRelativePath); + sourceExtension = path.getextension(sourceRelativePath); + end + local sourceHasA32Forced = sourceExtension == ".arm"; + if sourceHasA32Forced then + sourceRelativePath = path.removeextension(sourceRelativePath); + sourceExtension = path.getextension(sourceRelativePath); + end + if sourceExtension == ".arm" or sourceExtension == ".neon" then + p.warn( + "Workspace '%s' project '%s' config '%s' file '%s' has an incorrect armeabi suffix - " .. + "only .arm, .neon and .arm.neon are allowed by the NDK", + config.workspace.name, config.project.name, config.name, fileNode.abspath); + else + local sourceBaseName = path.getbasename(sourceRelativePath); + -- Check if the file name is not empty. + if sourceBaseName ~= "" then + -- Silently skipping files in the project unrelated to ndk-build, as well as headers. + if androidndk.sourceExtensions[sourceExtension] ~= nil then + -- Is a source file. + + -- LOCAL_SRC_FILES limitations for source files: + -- Allowed directly on Linux: !%+-./=?@[]^_{}~ + -- Allowed if \-escaped on Linux: * + -- (* works correctly on Linux, not as a wildcard.) + -- : is allowed if escaped on Linux, but is the drive separator on Windows, and is not escapable there. + -- Absolute paths are okay, so escaping should be disabled dynamically on Windows. + -- Allowed and escapable on Windows, disallowed on Linux: \ + -- UNC paths are not supported on Windows. + -- Disallowed: whitespaces, "#$&'(),;<>`| + if androidndk.staticallyHasPatternPostEsc(sourceOriginalRelativePath, "[%s\"#%$&'%(%),;<>`|]") then + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' file '%s' " .. + "with path containing whitespaces or disallowed characters \"#$&'(),;<>`|", + config.workspace.name, config.project.name, config.name, sourceOriginalRelativePath); + else + -- Try adding the file to source files. + local sourceFileTable = sourceFiles; + if sourceExtension == androidndk.asmExtension then + -- .asm (YASM) files can be assembled only for x86 and x86_64, giving warnings on other architectures. + -- YASM files are explicitly added only for x86 and x86_64 as otherwise ndk-build will be throwing warnings. + if configAbiSupportsYasm then + if configAbi == androidndk.ABI_ALL then + -- Add YASM sources conditionally for multi-ABI configurations. + sourceFileTable = sourceFilesYasmConditional; + else + -- Add YASM sources directly to the main LOCAL_SRC_FILES assignment for x86 and x86_64. + sourceFileTable = sourceFiles; + end + else + sourceFileTable = nil; + end + end + if sourceFileTable ~= nil and sourceFileTable[sourceRelativePath] == nil then + -- New source file. + + -- Add the file to the table. + -- Allowing A32 and NEON to be enabled for files using either the suffixes or file filters. + -- Considering the suffixes more important as they're always specified for individual files, while filters can be less granular. + local sourceRelativePathWithSuffixesParts = { sourceRelativePath }; + if not configUsesA32 and not sourceHasA32Forced then + sourceHasA32Forced = androidndk.configUsesA32(fileConfig); + end + if sourceHasA32Forced then + table.insert(sourceRelativePathWithSuffixesParts, ".arm"); + end + if not configUsesNeon and not sourceHasNeonForced then + sourceHasNeonForced = androidndk.configUsesNeon(fileConfig); + end + if sourceHasNeonForced then + table.insert(sourceRelativePathWithSuffixesParts, ".neon"); + end + local sourceRelativePathWithSuffixes = table.concat(sourceRelativePathWithSuffixesParts); + sourceFileTable[sourceRelativePath] = sourceRelativePathWithSuffixes; + table.insert(sourceFileTable, sourceRelativePathWithSuffixes); + + -- Gather include directories. + -- C_INCLUDES is used for C, C++, GAS and YASM (all non-RenderScript source types supported), but not for RenderScript. + local includeDirsVariable = iif(androidndk.rsExtensions[sourceExtension] ~= nil, renderScriptIncludeDirsVariable, "C_INCLUDES"); + local includeDirsForExtension = includeDirs[includeDirsVariable]; + if includeDirsForExtension == nil then + includeDirsForExtension = {}; + includeDirs[includeDirsVariable] = includeDirsForExtension; + end + if not configIncludeDirsAdded[includeDirsVariable] then + androidndk.addIncludeDirs(includeDirsForExtension, config); + end + -- Add extension-specific include directories. + -- ndk-build doesn't support per-file include directories, but C/C++/GAS/YASM and RenderScript need to be distinguished from each other. + androidndk.addIncludeDirs(includeDirsForExtension, fileConfig); + + -- Gather compiler flags. + local compilerFlagsVariable = androidndk.compilerFlagsVariables[sourceExtension]; + if compilerFlagsVariable ~= nil then + local compilerFlagsForExtension = compilerFlags[compilerFlagsVariable]; + if compilerFlagsForExtension == nil then + compilerFlagsForExtension = {}; + compilerFlags[compilerFlagsVariable] = compilerFlagsForExtension; + end + -- Add non-extension-specific compiler options from the project configuration if hasn't done already. + -- It's fine not to do filtering if the project, for example, contains only C or C++ files, but no assembly. + if not configCompilerFlagsAdded[compilerFlagsVariable] then + configCompilerFlagsAdded[compilerFlagsVariable] = true; + androidndk.addCompilerFlagsForExtension(compilerFlagsForExtension, sourceExtension, config, temporaryVariables); + end + -- Add extension-specific compiler options. + -- ndk-build doesn't support per-file flags, but C, C++, GAS and YASM need to be distinguished from each other. + -- Doing this after the project-level flags so per-extension flags can override them (provide a -fno- for an -f or the opposite). + androidndk.addCompilerFlagsForExtension(compilerFlagsForExtension, sourceExtension, fileConfig, temporaryVariables); + end + end + end + end + end + end + end + end + end + end + end + }); + + -- Get the extension, or nil if not available, for the special case of precreated project files. + -- These can be external Android.mk files and prebuilt libraries. + -- To use one, it must be the only source file in the project. + local precreatedExtension = nil; + if #sourcePrecreatedFiles == 1 and not (#sourceFiles > 0 or #sourceFilesYasmConditional > 0) then + precreatedExtension = path.getextension(sourcePrecreatedFiles[1]); + end + + if precreatedExtension ~= nil then + -- Check the special case of an external Android.mk file. + -- Using Android.mk files not generated by Premake is allowed as long as: + -- * Only one project is specified in it. + -- * The project name, after all escaping and reference expansion, is the same as LOCAL_MODULE in it. + -- * The kind matches the one used in it. + if precreatedExtension == ".mk" then + p.w("include %s", androidndk.preventWhitespaceTrimming(sourcePrecreatedFiles[1])); + return; + end + + -- Check the special case of a prebuilt library. + local prebuiltKind = androidndk.prebuiltKinds[config.kind]; + if prebuiltKind ~= nil and precreatedExtension == prebuiltKind.extension then + -- Backslash escaping is not supported for prebuilt library paths on Windows. + androidndk.assignToVariablePostEsc("LOCAL_SRC_FILES", sourcePrecreatedFiles[1], false); + p.w("include $(%s)", prebuiltKind.script); + return; + end + end + + -- Warn if there are any external Android.mk files or prebuilt libraries in the projects, but they aren't used for some reason. + -- It may be a mistake, the user might have wanted to use one, but has possibly configured the filters so that other files are still included. + for i, file in ipairs(sourcePrecreatedFiles) do + p.warn( + "Workspace '%s' project '%s' configuration '%s' contains an external Android.mk or a prebuilt library '%s' among its source files, " .. + "but it's not the only source file in the project, or it doesn't match the kind of the project", + config.workspace.name, config.project.name, config.name, file); + end + + -- Some variables may have values that are relevant only to a subset of the ABIs. + -- Examples are LOCAL_ARM_MODE, LOCAL_ARM_NEON, .arm/.neon/.arm.neon and .asm in LOCAL_SRC_FILES. + -- The general rule here is that most of the details of handling those are left to ndk-build itself. + -- This is done to avoid introducing additional interference and constraints, as well as to simplify the code. + -- ABI conditionals are added only when not having them would cause errors or warnings (like x86-specific CFLAGS on ARM). + -- If the default behavior of ndk-build needs to be overriden, the project should explicitly use the needed filters. + + -- Gather C++ settings if the precompiled header is used, from the project and, if available, the file. + -- The precompiled header is always compiled as C++ by ndk-build. + -- Treat it as potentially encountering a C++ file for the first time, so it's compiled with the needed include directories and flags. + -- Ignoring ExcludeFromBuild as it doesn't have effect on pchheader in other Premake actions. + if pch ~= nil then + if not configIncludeDirsAdded["C_INCLUDES"] then + configIncludeDirsAdded["C_INCLUDES"] = true; + local cIncludes = includeDirs["C_INCLUDES"]; + if cIncludes == nil then + cIncludes = {}; + includeDirs["C_INCLUDES"] = cIncludes; + end + androidndk.addIncludeDirs(cIncludes, config); + if pchFileConfig ~= nil then + androidndk.addIncludeDirs(cIncludes, pchFileConfig); + end + end + if not configCompilerFlagsAdded["CPPFLAGS"] then + configCompilerFlagsAdded["CPPFLAGS"] = true; + local cppFlags = compilerFlags["CPPFLAGS"]; + if cppFlags == nil then + cppFlags = {}; + compilerFlags["CPPFLAGS"] = cppFlags; + end + androidndk.addCompilerFlagsForExtension(cppFlags, androidndk.cppExtensions[1], config, temporaryVariables); + if pchFileConfig ~= nil then + androidndk.addCompilerFlagsForExtension(cppFlags, androidndk.cppExtensions[1], pchFileConfig, temporaryVariables); + end + end + end + + -- If the LinkTimeOptimization flag is enabled, apply the LTO type requested by the project explicitly via `buildoptions`, or the default "-flto". + -- Also verify link-time optimization type consistency. + local fltoCOverride = nil; + if compilerFlags["CONLYFLAGS"] ~= nil then + fltoCOverride = compilerFlags["CONLYFLAGS"].flto; + end + local fltoCppOverride = nil; + if compilerFlags["CPPFLAGS"] ~= nil then + fltoCppOverride = compilerFlags["CPPFLAGS"].flto; + end + -- This must not be used by the link options - they should be only verified against it. + -- Static library analysis doesn't scan `buildoptions`, only `linkoptions`. + local fltoBuildOption = fltoCppOverride or fltoCOverride or "-flto"; + if fltoCOverride ~= nil and fltoCOverride ~= fltoBuildOption then + p.warn( + "Workspace '%s' project '%s' configuration '%s' uses LTO type '%s' for C, while it uses '%s' for another language", + config.workspace.name, config.project.name, config.name, fltoCOverride, fltoBuildOption); + end + if fltoCppOverride ~= nil and fltoCppOverride ~= fltoBuildOption then + p.warn( + "Workspace '%s' project '%s' configuration '%s' uses LTO type '%s' for C++, while it uses '%s' for another language", + config.workspace.name, config.project.name, config.name, fltoCppOverride, fltoBuildOption); + end + if config.flags.LinkTimeOptimization then + if fltoCOverride == nil then + if compilerFlags["CONLYFLAGS"] == nil then + compilerFlags["CONLYFLAGS"] = {} + end + if compilerFlags["CONLYFLAGS"].buildoptions == nil then + compilerFlags["CONLYFLAGS"].buildoptions = {} + end + table.insertkeyed(compilerFlags["CONLYFLAGS"].buildoptions, fltoBuildOption); + end + if fltoCppOverride == nil then + if compilerFlags["CPPFLAGS"] == nil then + compilerFlags["CPPFLAGS"] = {} + end + if compilerFlags["CPPFLAGS"].buildoptions == nil then + compilerFlags["CPPFLAGS"].buildoptions = {} + end + table.insertkeyed(compilerFlags["CPPFLAGS"].buildoptions, fltoBuildOption); + end + end + + -- Gather linker options (LDFLAGS and LDLIBS) from the module and its static library dependencies. + -- Importing manually instead of using LOCAL_EXPORT_LDFLAGS/LDLIBS so there's no transitivity across shared libraries. + -- This is consistent with the behavior of Visual Studio - only static libraries export their dependencies. + -- Also it keeps syslibdirs of all static library dependencies last, after all local library directories. + -- ndk-build puts imported LDFLAGS first, but imported LDLIBS last. + -- For -u, which is usually passed via LDFLAGS, the order of local vs. imported options doesn't matter, however. + -- all_depends in ndk-build is generated via a breadth-first walk of the dependency graph. + local isLinked = config.kind == p.CONSOLEAPP or config.kind == p.SHAREDLIB; + local ldFlags = {}; + local ldLibs = {}; + if isLinked then + local ldFlagsUndefined = {}; + local ldFlagsLocal = {}; + local ldLibsDirFlags = {}; + local ldLibsSysDirFlags = {}; + local ldLibsLibFlags = {}; + local ldLinkTimeOptimization = config.flags.LinkTimeOptimization; + local ldFlagsExplicitFlto = nil; + -- Start recursion from the current project config. + -- It will also result in local LDLIBS parts being added before the imports. + local ldDependencies = { config, [config.project.name] = config }; + local ldDependencyIndex = 1; + while ldDependencyIndex <= #ldDependencies do + -- Gather -u LDFLAGS as well as LDLIBS parts for the project currently being processed. + -- -u may be used to keep certain exports from static libraries in shared libraries, in a way more granular than whole static libraries. + -- It is used, for example, to prevent stripping ANativeActivity_onCreate from the Android Native App Glue. + local ldDependency = ldDependencies[ldDependencyIndex]; + local ldDependencyExplicitFlto = + androidndk.addSingleConfigLinkerFlags( + ldDependency, ldFlagsUndefined, nil, ldLibsDirFlags, ldLibsSysDirFlags, ldLibsLibFlags, temporaryVariables); + -- The last -flto has the highest priority. + -- However, -flto for the project itself is actually added after all dependencies here, not alongside them. + if ldDependency ~= config and ldDependencyExplicitFlto ~= nil then + if ldFlagsExplicitFlto ~= nil and ldFlagsExplicitFlto ~= ldDependencyExplicitFlto then + p.warn( + "Workspace '%s' project '%s' configuration '%s' has mismatching -flto link options " .. + "among its static library dependencies ('%s' in project '%s' and '%s' in another project)", + config.workspace.name, config.project.name, config.name, + ldDependencyExplicitFlto, ldDependency.project.name, ldFlagsExplicitFlto); + end + ldFlagsExplicitFlto = ldDependencyExplicitFlto; + end + for i, ldDependencySibling in ipairs(p.config.getlinks(ldDependency, "siblings", "object")) do + -- Only gather -u exports and as LDLIBS from static libraries, as shared libraries have already been linked as a complete object. + -- Also check if not already handled, including to prevent circular dependencies. + if ldDependencySibling.kind == p.STATICLIB and ldDependencies[ldDependencySibling.project.name] == nil then + if androidndk.isProjectSupported(ldDependencySibling.project, false) then + if androidndk.isConfigSupported(ldDependencySibling, false) then + table.insert(ldDependencies, ldDependencySibling); + ldDependencies[ldDependencySibling.project.name] = ldDependencySibling; + if ldDependencySibling.flags.LinkTimeOptimization then + ldLinkTimeOptimization = true; + end + end + end + end + end + ldDependencyIndex = ldDependencyIndex + 1; + end + -- Merge LDLIBS parts. + local ldLibsFlagsTables = { ldLibsDirFlags, ldLibsSysDirFlags, ldLibsLibFlags }; + for i, ldLibsFlags in ipairs(ldLibsFlagsTables) do + for i2, ldLibsFlag in ipairs(ldLibsFlags) do + table.insertkeyed(ldLibs, ldLibsFlag); + end + end + -- Gather local LDFLAGS other than -u after the imports. + -- For LDFLAGS, the order mostly shouldn't matter though. + -- Doing this just in a way consistent with how ndk-build itself gathers exported LDLIBS. + local ldLocalFlagsExplicitFlto = androidndk.addSingleConfigLinkerFlags(config, nil, ldFlagsLocal, nil, nil, nil, temporaryVariables); + if ldLocalFlagsExplicitFlto ~= nil then + -- The last -flto has the highest priority. + if ldFlagsExplicitFlto ~= nil and ldFlagsExplicitFlto ~= ldLocalFlagsExplicitFlto then + p.warn( + "Workspace '%s' project '%s' configuration '%s' has a -flto link option '%s' " .. + "that doesn't match its value used by static library dependencies '%s'", + config.workspace.name, config.project.name, config.name, + ldLocalFlagsExplicitFlto, ldFlagsExplicitFlto); + end + ldFlagsExplicitFlto = ldLocalFlagsExplicitFlto; + end + if ldFlagsExplicitFlto ~= nil then + ldLinkTimeOptimization = true; + end + -- If the project itself, or any of its static libraries, is compiled with LTO, link with LTO. + -- Also, if the project or any of its static libraries has an explicit -flto option (possibly with a non-default type), use it instead. + local ldFlagsFlto = ldFlagsExplicitFlto or "-flto"; + -- Checking if the build -flto is not nil because the project may, for instance, only link against static libraries with LTO. + if fltoBuildOption ~= nil and fltoBuildOption ~= ldFlagsFlto then + if ldFlagsFlto ~= nil then + p.warn( + "Workspace '%s' project '%s' configuration '%s' uses a -flto build option '%s' " .. + "that differs from the value '%s' that will be used by the linker", + config.workspace.name, config.project.name, config.name, fltoBuildOption, ldFlagsFlto); + else + p.warn( + "Workspace '%s' project '%s' configuration '%s' uses a -flto build option '%s', " .. + "but doesn't have the LinkTimeOptimization flag enabled or an -flto in its link options", + config.workspace.name, config.project.name, config.name, fltoBuildOption); + end + end + if ldLinkTimeOptimization then + table.insertkeyed(ldFlags, ldFlagsFlto); + end + -- Merge LDFLAGS parts. + local ldFlagsTables = { ldFlagsUndefined, ldFlagsLocal }; + for i, ldFlagsTable in ipairs(ldFlagsTables) do + for i2, ldFlag in ipairs(ldFlagsTable) do + table.insertkeyed(ldFlags, ldFlag); + end + end + end + + -- Gather module dependencies for the current build configuration and platform. + local sharedLibraries = {}; + local staticLibraries = {}; + local wholeStaticLibraries = {}; + for i, siblingLinkProjectConfig in ipairs(p.config.getlinks(config, "siblings", "object")) do + if androidndk.isProjectSupported(siblingLinkProjectConfig.project, false) then + if androidndk.isConfigSupported(siblingLinkProjectConfig, false) then + local siblingLinksTable = nil; + if siblingLinkProjectConfig.kind == p.SHAREDLIB then + siblingLinksTable = sharedLibraries; + elseif siblingLinkProjectConfig.kind == p.STATICLIB then + if siblingLinkProjectConfig.wholelib == true or config.wholelibs[siblingLinkProjectConfig.project.name] ~= nil then + siblingLinksTable = wholeStaticLibraries; + else + siblingLinksTable = staticLibraries; + end + end + if siblingLinksTable ~= nil then + table.insert(siblingLinksTable, siblingLinkProjectConfig.project.name); + end + end + end + end + + -- Assign the values to the variables. + + -- Write assignments to referenced intermediate variables. + for i, temporaryVariable in ipairs(temporaryVariables) do + androidndk.assignToVariablePostEsc(androidndk.temporaryVariablePrefix .. i, temporaryVariable, false); + end + + -- LOCAL_ALLOW_UNDEFINED_SYMBOLS + if isLinked and config.undefinedsymbols == true then + p.w("LOCAL_ALLOW_UNDEFINED_SYMBOLS := true"); + end + + -- LOCAL_ASFLAGS + androidndk.writeProjectCompilerFlags(compilerFlags, "ASFLAGS", true); + + -- LOCAL_ASMFLAGS + androidndk.writeProjectCompilerFlags(compilerFlags, "ASMFLAGS", true); + + -- LOCAL_ARM_MODE + -- NDK uses T32 by default, and always switches to A32 for debug builds, but it's handled implicitly in it. + if configUsesA32 then + p.w("LOCAL_ARM_MODE := arm"); + end + + -- LOCAL_ARM_NEON + -- On NDK r21, NEON is enabled by default. + -- Since that wasn't the case on older versions, both enabling and disabling it explicitly anyway. + p.w("LOCAL_ARM_NEON := %s", iif(configUsesNeon, "true", "false")); + + -- LOCAL_CONLYFLAGS + -- Potentially will be appending ISA and vector extensions, post-process later. + local cOnlyFlagsNeedPostProcess = androidndk.writeProjectCompilerFlags(compilerFlags, "CONLYFLAGS", false); + + -- LOCAL_CPPFLAGS + -- Potentially will be appending ISA and vector extensions, post-process later. + local cppFlagsNeedPostProcess = androidndk.writeProjectCompilerFlags(compilerFlags, "CPPFLAGS", false); + + -- After writing LOCAL_CONLYFLAGS and LOCAL_CPPFLAGS, add ABI-specific C and C++ flags (not using LOCAL_CFLAGS at all). + local cFlagsAbis; + if configAbi == androidndk.ABI_ALL then + cFlagsAbis = androidndk.knownAbis; + else + cFlagsAbis = { configAbi }; + end + local abiCFlagsConditionalOpen = false; + for i, cFlagsAbi in ipairs(cFlagsAbis) do + local abiCFlags = {}; + -- vectorextensions + if config.vectorextensions ~= nil then + local abiVectorExtensions = androidndk.abiVectorExtensions[cFlagsAbi]; + if abiVectorExtensions ~= nil then + local abiConfigVectorExtensions = abiVectorExtensions[config.vectorextensions]; + if abiConfigVectorExtensions ~= nil then + table.insertkeyed(abiCFlags, p.esc(abiConfigVectorExtensions)); + end + end + end + -- isaextensions + local abiIsaExtensions = androidndk.abiIsaExtensions[cFlagsAbi]; + if abiIsaExtensions ~= nil then + for i2, extension in ipairs(config.isaextensions) do + local abiIsaExtension = abiIsaExtensions[extension]; + if abiIsaExtension ~= nil then + table.insertkeyed(abiCFlags, p.esc(abiIsaExtension)); + end + end + end + -- Write the conditional and the extensions. + if #abiCFlags > 0 then + if configAbi == androidndk.ABI_ALL then + p.push("%sifeq ($(TARGET_ARCH_ABI),%s)", androidndk.getCaseElse(abiCFlagsConditionalOpen), cFlagsAbi); + abiCFlagsConditionalOpen = true; + end + -- Safe to += even if no other CONLYFLAGS and CPPFLAGS were specified, set to an empty value by CLEAR_VARS. + if androidndk.assignToVariablePostEsc("LOCAL_CONLYFLAGS", abiCFlags, true, androidndk.shellEscapePostQuotesChecker, "+=") then + cOnlyFlagsNeedPostProcess = true; + end + if androidndk.assignToVariablePostEsc("LOCAL_CPPFLAGS", abiCFlags, true, androidndk.shellEscapePostQuotesChecker, "+=") then + cppFlagsNeedPostProcess = true; + end + if configAbi == androidndk.ABI_ALL then + p.pop(); + end + end + end + if abiCFlagsConditionalOpen then + assert(configAbi == androidndk.ABI_ALL, "Can have ABI conditionals for ABI-specific C flags only for ABI-agnostic configurations"); + p.w("endif"); + end + + if cOnlyFlagsNeedPostProcess then + androidndk.writePostProcessCall("LOCAL_CONLYFLAGS", androidndk.shellEscapePostQuotesMakeCall); + end + if cppFlagsNeedPostProcess then + androidndk.writePostProcessCall("LOCAL_CPPFLAGS", androidndk.shellEscapePostQuotesMakeCall); + end + + -- LOCAL_CPP_FEATURES + -- For Clang, Premake core modules check if these are not "Off" (rather than if they are "On"). + local cppFeatures = {}; + if config.exceptionhandling ~= p.OFF then + table.insert(cppFeatures, "exceptions"); + end + if config.rtti ~= p.OFF then + table.insert(cppFeatures, "rtti"); + end + if #cppFeatures > 0 then + androidndk.assignToVariablePostEsc("LOCAL_CPP_FEATURES", cppFeatures, false); + end + + -- LOCAL_C_INCLUDES + androidndk.writeProjectIncludeDirs(includeDirs, "C_INCLUDES", true); + + -- LOCAL_DISABLE_FATAL_LINKER_WARNINGS + -- Different than the Android.mk default (fatal by default), but for compatibility with other Premake actions. + if isLinked and not config.flags.FatalLinkWarnings then + p.w("LOCAL_DISABLE_FATAL_LINKER_WARNINGS := true"); + end + + -- LOCAL_DISABLE_FORMAT_STRING_CHECKS + -- Enabling format string checks by default (if nil). + if config.formatstringchecks == false then + p.w("LOCAL_DISABLE_FORMAT_STRING_CHECKS := true"); + end + + -- LOCAL_LDFLAGS + if isLinked then + if androidndk.assignToVariablePostEsc("LOCAL_LDFLAGS", ldFlags, true, androidndk.shellEscapePostQuotesChecker) then + androidndk.writePostProcessCall("LOCAL_LDFLAGS", androidndk.shellEscapePostQuotesMakeCall); + end + end + + -- LOCAL_LDLIBS + if isLinked then + if androidndk.assignToVariablePostEsc("LOCAL_LDLIBS", ldLibs, true, androidndk.shellEscapePostQuotesChecker) then + androidndk.writePostProcessCall("LOCAL_LDLIBS", androidndk.shellEscapePostQuotesMakeCall); + end + end + + -- LOCAL_MODULE_FILENAME + -- Allowed directly on Linux: !+,-.=@[]^_{}~ + -- Allowed if \-escaped on Linux: "\` + -- Allowed if \-escaped on Linux, misleadingly displayed with \ in the terminal: &'()*<>? + -- : is allowed if escaped on Linux (displayed with \), disallowed on Windows (drive letter separator - only the name, not a path, in this variable). + -- Disallowed: whitespaces, #$%/;| + local moduleFileName = p.esc(config.buildtarget.prefix) .. p.esc(config.buildtarget.basename) .. p.esc(config.buildtarget.suffix); + if androidndk.staticallyHasPatternPostEsc(moduleFileName, "[%s#%$%%/;|]") then + -- % needs to be escaped as %% in printf format strings. + p.warn( + "Using the default build target name for workspace '%s' project '%s' configuration '%s' " .. + "instead of '%s' (displayed as escaped) with whitespaces or disallowed characters #$%%/:;|", + config.workspace.name, config.project.name, config.name, moduleFileName); + else + if androidndk.assignToVariablePostEsc("LOCAL_MODULE_FILENAME", moduleFileName, false, androidndk.shellEscapeModuleFileNameChecker) then + androidndk.writePostProcessCall("LOCAL_MODULE_FILENAME", androidndk.shellEscapeModuleFileNameMakeCall); + end + end + + -- LOCAL_PCH + if pch ~= nil then + if androidndk.assignToVariablePostEsc("LOCAL_PCH", pch, false, androidndk.shellEscapeChecker) then + androidndk.writePostProcessCall("LOCAL_PCH", androidndk.shellEscapeMakeCall); + end + end + + -- LOCAL_RENDERSCRIPT_COMPATIBILITY + if config.renderscriptcompatibility == true then + p.w("LOCAL_RENDERSCRIPT_COMPATIBILITY := true"); + end + + -- LOCAL_RENDERSCRIPT_FLAGS + androidndk.writeProjectCompilerFlags(compilerFlags, "RENDERSCRIPT_FLAGS", true); + + -- LOCAL_RENDERSCRIPT_INCLUDES or LOCAL_RENDERSCRIPT_INCLUDES_OVERRIDE + androidndk.writeProjectIncludeDirs(includeDirs, renderScriptIncludeDirsVariable, true); + + -- LOCAL_SHARED_LIBRARIES + -- See LOCAL_MODULE for character rules. + if androidndk.assignToVariablePostEsc("LOCAL_SHARED_LIBRARIES", p.esc(sharedLibraries), true, androidndk.shellEscapeChecker) then + androidndk.writePostProcessCall("LOCAL_SHARED_LIBRARIES", androidndk.shellEscapeMakeCall); + end + + -- LOCAL_SHORT_COMMANDS + if config.shortcommands == true then + p.w("LOCAL_SHORT_COMMANDS := true"); + end + + -- LOCAL_SRC_FILES + -- See the validation of source files for information about disallowed characters. + local sourceFilesNeedPostProcess = false; + if androidndk.assignToVariablePostEsc("LOCAL_SRC_FILES", sourceFiles, true, androidndk.shellEscapeSrcFilesChecker) then + sourceFilesNeedPostProcess = true; + end + -- Only add .asm files for x86 and x86_64 as otherwise the NDK will be throwing warnings. + -- Single-ABI configurations are already handled while gathering the source file nodes. + -- Write the assignment for multi-ABI configurations. + if #sourceFilesYasmConditional > 0 then + p.push("ifneq ($(filter x86 x86_64,$(TARGET_ARCH_ABI)),)"); + if androidndk.assignToVariablePostEsc("LOCAL_SRC_FILES", sourceFilesYasmConditional, true, androidndk.shellEscapeSrcFilesChecker, "+=") then + sourceFilesNeedPostProcess = true; + end + p.pop("endif"); + end + if sourceFilesNeedPostProcess then + androidndk.writePostProcessCall("LOCAL_SRC_FILES", androidndk.shellEscapeSrcFilesMakeCall); + end + + -- LOCAL_STATIC_LIBRARIES + -- See LOCAL_MODULE for character rules. + if androidndk.assignToVariablePostEsc("LOCAL_STATIC_LIBRARIES", p.esc(staticLibraries), true, androidndk.shellEscapeChecker) then + androidndk.writePostProcessCall("LOCAL_STATIC_LIBRARIES", androidndk.shellEscapeMakeCall); + end + + -- LOCAL_THIN_ARCHIVE + if config.kind == p.STATICLIB and config.thinarchive == true then + p.w("LOCAL_THIN_ARCHIVE := true"); + end + + -- LOCAL_WHOLE_STATIC_LIBRARIES + -- See LOCAL_MODULE for character rules. + if androidndk.assignToVariablePostEsc("LOCAL_WHOLE_STATIC_LIBRARIES", p.esc(wholeStaticLibraries), true, androidndk.shellEscapeChecker) then + androidndk.writePostProcessCall("LOCAL_WHOLE_STATIC_LIBRARIES", androidndk.shellEscapeMakeCall); + end + + -- Invoke the build script for the specified kind. + local kindScript = androidndk.kindScripts[config.kind]; + assert(kindScript ~= nil, "Configurations with invalid kinds must be skipped in isConfigSupported"); + p.w("include $(%s)", kindScript); +end + +function androidndk.onProject(project) + if not androidndk.isProjectSupported(project, true) then + return; + end + androidndk.setupGeneration(); + p.generate(project, ".prj.Android.mk", function(project) + -- Must be synchronized with getEscapedProjectRelativePath. + -- Using the Android.mk directory (location), not the base directory, as it's less likely to contain header files. + -- There's undesirable behavior in NDK regarding the order of include directories. + -- LOCAL_PATH is always implicitly an include directory, and the -I for it is passed after the STL include directories. + -- That causes #include_next in C standard library headers to locate app headers with the same in LOCAL_PATH. + -- https://github.com/android/ndk/issues/1582 + p.w("LOCAL_PATH := $(call my-dir)"); + p.w("include $(CLEAR_VARS)"); + -- Workaround for LOCAL_RENDERSCRIPT_COMPATIBILITY not being cleared in NDK r23 as it's not listed in modules-LOCALS. + -- Clearing similar to clear-vars as of NDK r23, by defining as empty, not via `undefine`. + p.w("LOCAL_RENDERSCRIPT_COMPATIBILITY :="); + -- Project name already verified by androidndk.isProjectSupported. + if androidndk.assignToVariablePostEsc("LOCAL_MODULE", p.esc(project.name), false, androidndk.shellEscapeChecker) then + androidndk.writePostProcessCall("LOCAL_MODULE", androidndk.shellEscapeMakeCall); + end + -- Specify all extensions assumed to be C++ and RenderScript by this module. + -- This allows for using extensions different than the default in this module if needed. + -- Additionally, it ensures forward compatibility if the defaults in ndk-build are changed. + androidndk.assignToVariablePostEsc("LOCAL_CPP_EXTENSION", table.concat(androidndk.cppExtensions, " "), false); + androidndk.assignToVariablePostEsc("LOCAL_RS_EXTENSION", table.concat(androidndk.rsExtensions, " "), false); + -- Gather valid configurations for each ABI. + -- ABI-agnostic configurations are checked last. + -- This allows both ABI specializations and ABI-agnostic fallbacks to be specified for building at the same time. + -- Without this, the ABI-agnostic fallback would've been chosen over specializations depending on the order of the platforms table. + local abisConfigs = {}; + for config in p.project.eachconfig(project) do + if androidndk.isConfigSupported(config, true) then + local configAbi = androidndk.getConfigAbi(config); + if abisConfigs[configAbi] == nil then + abisConfigs[configAbi] = { abi = configAbi, configs = {} }; + -- The ABI-agnostic configurations will be used as fallbacks when no ABI specialization is available. + -- So, they need to be generated last. + if configAbi ~= androidndk.ABI_ALL then + table.insert(abisConfigs, abisConfigs[configAbi]); + end + end + table.insert(abisConfigs[configAbi].configs, config); + end + end + if abisConfigs[androidndk.ABI_ALL] ~= nil then + table.insert(abisConfigs, abisConfigs[androidndk.ABI_ALL]); + end + local anyConfigWritten = false; + for i, abiConfigs in ipairs(abisConfigs) do + for i2, abiConfig in ipairs(abiConfigs.configs) do + local configCondition = androidndk.getConfigCondition(abiConfig.buildcfg, abiConfig.platform, abiConfigs.abi); + p.push("%s%s", androidndk.getCaseElse(anyConfigWritten), configCondition); + androidndk.generateProjectConfig(abiConfig); + p.pop(); + anyConfigWritten = true; + end + end + if anyConfigWritten then + p.w("endif"); + end + end); +end diff --git a/src_rebuild/premake_modules/androidndk/androidndk_workspace.lua b/src_rebuild/premake_modules/androidndk/androidndk_workspace.lua new file mode 100644 index 00000000..1f737045 --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/androidndk_workspace.lua @@ -0,0 +1,513 @@ +-- premake-androidndk - a multi-ABI, multi-language ndk-build Android.mk generator for Premake. +-- Originally written by Vitaliy Kuzmin. +-- See README.md for detailed usage and design information. +-- Available under the Unlicense (see UNLICENSE.txt) or the BSD 3-Clause "New" or "Revised" License (see LICENSE.txt). + +local p = premake; +local androidndk = p.modules.androidndk; + +-- Projects can add their own configurations and platforms. +-- So, the use of workspace.configurations, workspace.projects, and their product workspace.configs, should be avoided. +-- Instead, configs should be gathered from all projects. + +-- Writes the setup of the variables used in configuration conditionals. +-- Required in both Application.mk and the workspace Android.mk because with Gradle, the Application.mk is optional. +function androidndk.setupConfigConditionalMakeVariables(usedBuildcfgs, usedPlatforms) + -- Display generated workspace usage information. + -- Using warning/error directly, not the __ndk_* counterparts. + -- __ndk_* functions are internal, and it's more important to show this to the user than to respect NDK_NO_*. + -- $(info) is not displayed in the build output in Android Studio, using $(warning) for everything. + p.push("ifeq ($(filter %s,$(PREMAKE_ANDROIDNDK_CONFIGURATIONS)),)", table.concat(p.esc(usedBuildcfgs), " ")); + p.w("$(warning No configurations to build are specified in PREMAKE_ANDROIDNDK_CONFIGURATIONS.)"); + p.w("$(warning Specify one or multiple \"PREMAKE_ANDROIDNDK_CONFIGURATIONS+=configuration\" in the ndk-build arguments to build those configurations.)"); + p.w("$(warning For workspaces with multiple target platforms, you can also provide the list of the platforms to build via PREMAKE_ANDROIDNDK_PLATFORMS.)"); + p.w("$(warning If PREMAKE_ANDROIDNDK_PLATFORMS is not specified, the projects in this workspace will be built for all platforms they're targeting.)"); + p.w("$(warning Note that configuration and platform names are case-sensitive in this script.)"); + p.w("$(warning )"); + p.w("$(warning It's heavily recommended that only at most one configuration and platform specified corresponds to each targeted ABI.)"); + p.w("$(warning Otherwise, which set of project settings will actually be chosen for building for each ABI will be undefined.)"); + p.w("$(warning )"); + p.w("$(warning The recommended approach is to use configurations for the optimization mode, and, if needed, platforms for ABI filtering.)"); + p.w("$(warning This can be done by specifying different values of the Premake architecture setting for each platform using a platform filter.)"); + p.w("$(warning In this case, each ABI will unambiguously correspond to only one configuration and platform pair.)"); + p.w("$(warning )"); + p.w("$(warning Configurations for this workspace:)"); + for i, buildcfg in ipairs(usedBuildcfgs) do + p.x("$(warning $() %s)", buildcfg); + end + p.w("$(warning )"); + if #usedPlatforms > 0 then + p.w("$(warning Platforms for this workspace:)"); + for i, platform in ipairs(usedPlatforms) do + p.x("$(warning $() %s)", platform); + end + else + p.w("$(warning This workspace is platform-agnostic.)"); + end + p.w("$(error Aborting.)"); + p.pop("endif"); + + -- Generate variables used for configuration selection. + -- As Application.mk variables are preserved in Android.mk, += may result in duplication. + -- Only using := for this reason, and also not modifying the original PREMAKE_ANDROIDNDK_CONFIGURATIONS/PLATFORMS. + p.w("PREMAKE_ANDROIDNDK_CONFIGURATIONS_PREFIXED := $(addprefix CONFIGURATION=,$(PREMAKE_ANDROIDNDK_CONFIGURATIONS))"); + if #usedPlatforms > 0 then + p.push("ifneq ($(PREMAKE_ANDROIDNDK_PLATFORMS),)"); + p.w("PREMAKE_ANDROIDNDK_PLATFORMS_PREFIXED := $(addprefix PLATFORM=,$(PREMAKE_ANDROIDNDK_PLATFORMS))"); + p.pop(); + p.push("else"); + local usedPlatformsPrefixed = {}; + for i, platform in ipairs(usedPlatforms) do + table.insert(usedPlatformsPrefixed, "PLATFORM=" .. p.esc(platform)); + end + p.w("PREMAKE_ANDROIDNDK_PLATFORMS_PREFIXED := %s", table.concat(usedPlatformsPrefixed, " ")); + p.pop("endif"); + end +end + +function androidndk.findConfigForAppSetting(workspace, buildcfg, platform, predicate) + -- In none of these checks should completely unrequested build configurations or platforms pass. + -- This may lead, for instance, to systemversion for a totally different OS to be selected as a fallback. + + -- Considering the workspace (and, if it doesn't provide the needed value, the start project) highest-priority. + -- Even when requesting platform-specific settings, but the start project is platform-agnostic (nil .platform), still prefer the start project. + -- This makes settings more controllable than when some project can override the setting just because it has a platform specialization. + + -- Try to find in the workspace itself. + for config in p.workspace.eachconfig(workspace) do + if androidndk.isConfigSupported(config, false) then + if config.buildcfg == buildcfg and (config.platform == nil or config.platform == platform) and predicate(config) then + return config; + end + end + end + + -- If didn't find in the workspace itself, try the main project in the workspace - the start project. + if workspace.startproject ~= nil then + local startProject = p.workspace.findproject(workspace, workspace.startproject); + if startProject ~= nil and androidndk.isProjectSupported(startProject, false) then + for config in p.project.eachconfig(startProject) do + if androidndk.isConfigSupported(config, false) then + if config.buildcfg == buildcfg and (config.platform == nil or config.platform == platform) and predicate(config) then + return config; + end + end + end + end + end + + -- This configuration may be used by just a subset of the projects. + -- Platform-specific specializations and platform-agnostic fallbacks are obtained via separate findConfigForAppSetting calls in this case. + -- ?= for platform-agnostic variables is done even if platform conditions pass - no need to look for them here for non-nil platforms. + for project in p.workspace.eachproject(workspace) do + if androidndk.isProjectSupported(project, false) then + for config in p.project.eachconfig(project) do + if androidndk.isConfigSupported(config, false) then + if config.buildcfg == buildcfg and config.platform == platform and predicate(config) then + return config; + end + end + end + end + end + + -- Found nothing - don't add the setting. + return nil; +end + +-- Returns a table of { variable = "APP_...", value = ... }, with the variable values escaped. +-- Expecting the outer code to set APP_DEBUG to false if APP_OPTIM is release in the end to match Premake optimize/symbols logic. +function androidndk.gatherEscapedAppVariables(workspace, buildcfg, platform) + -- If the value for a Premake setting is not specified by the projects for this configuration, the default must not be added to the table. + -- This would make it impossible for another configuration, where the setting is explicitly defined, to override the value using ?=. + -- Only returning variables corresponding to settings explicitly defined for this configuration. + + -- ndk-build defaults to non-debug (potentially no symbols) and optimized. + -- Premake doesn't define a default for symbols, but unoptimized by default. + -- Replicating the p.config.isDebugBuild and p.config.isOptimizedBuild logic. + -- An optimized build is not considered a debug one at all, regardless of whether symbols are needed. + local optimizeConfig = androidndk.findConfigForAppSetting(workspace, buildcfg, platform, function(config) + return config.optimize ~= nil; + end); + -- optimizeConfig.optimize ~= nil checked previously. + local isOptimized = optimizeConfig ~= nil and optimizeConfig.optimize ~= p.OFF and optimizeConfig.optimize ~= "Debug"; + local appOptim = nil; + if optimizeConfig ~= nil then + -- The configuration explicitly wants to enable or to disable optimizations. + appOptim = iif(isOptimized, "release", "debug"); + end + -- Expecting the outer code to set APP_DEBUG to false if APP_OPTIM is release. + -- Thus, not explicitly returning APP_DEBUG ?= false for release APP_OPTIM. + local appDebug = nil; + if not isOptimized then + local symbolsConfig = androidndk.findConfigForAppSetting(workspace, buildcfg, platform, function(config) + return config.symbols ~= nil; + end); + if symbolsConfig ~= nil then + -- p.config.isOptimizedBuild checked previously. + appDebug = iif(symbolsConfig.symbols ~= p.OFF and symbolsConfig.symbols ~= p.DEFAULT, "true", "false"); + end + end + + local appVariables = {}; + + -- APP_DEBUG + if appDebug ~= nil then + table.insert(appVariables, { variable = "APP_DEBUG", value = appDebug }); + end + + -- APP_OPTIM + if appOptim ~= nil then + table.insert(appVariables, { variable = "APP_OPTIM", value = appOptim }); + end + + -- APP_PLATFORM + local systemVersionConfig = androidndk.findConfigForAppSetting(workspace, buildcfg, platform, function(config) + return p.project.systemversion(config) ~= nil; + end); + if systemVersionConfig ~= nil then + table.insert(appVariables, { variable = "APP_PLATFORM", value = "android-" .. p.esc(p.project.systemversion(systemVersionConfig)) }); + end + + -- PREMAKE_ANDROIDNDK_APP_STL_RUNTIME (part of APP_STL) + local stlConfig = androidndk.findConfigForAppSetting(workspace, buildcfg, platform, function(config) + return config.cppstl ~= nil; + end); + if stlConfig ~= nil then + -- The default, which in ndk-build is "none", is explicitly requested for this configuration. + local stl = iif(stlConfig.cppstl == p.DEFAULT, "none", stlConfig.cppstl); + table.insert(appVariables, { variable = "PREMAKE_ANDROIDNDK_APP_STL_RUNTIME", value = p.esc(stl) }); + end + + -- PREMAKE_ANDROIDNDK_APP_STL_LINKAGE (part of APP_STL) + -- While for ndk-build, the default STL is "none", which doesn't have a linkage suffix, for CMake it's "c++_static". + -- Therefore, considering static the default (treating explicitly specified "Default" as "On"). + local staticRuntimeConfig = androidndk.findConfigForAppSetting(workspace, buildcfg, platform, function(config) + return config.staticruntime ~= nil; + end); + if staticRuntimeConfig ~= nil then + local stlLinkage = iif(staticRuntimeConfig.staticruntime ~= p.OFF, "_static", "_shared"); + table.insert(appVariables, { variable = "PREMAKE_ANDROIDNDK_APP_STL_LINKAGE", value = stlLinkage }); + end + + return appVariables; +end + +function androidndk.onWorkspace(workspace) + -- Not allowing references or escaped $ in workspace names as they complicate path building as well as specifying APP_BUILD_SCRIPT. + -- Premake treats any path beginning with $ as absolute. + -- APP_BUILD_SCRIPT also doesn't allow #$ and whitespaces. + if string.find(workspace.name, "%$") ~= nil or androidndk.staticallyHasPatternPostEsc(p.esc(workspace.name), "[%s#%$]") then + p.warn("Skipping workspace '%s' with name containing GNU make references, whitespaces or disallowed characters #$", workspace.name); + return; + end + + androidndk.setupGeneration(); + + -- Gather build configurations and platforms used by all projects for generating conditionals for application settings. + -- Projects may add their configurations not present in the workspace itself, and they won't appear in workspace.configs. + -- Some projects may have no platforms specified. + -- In this case, they're built regardless of the platform, and also if some completely different platform is specified. + -- At least one build configuration is required in a project, however. + local usedBuildcfgs = {}; + local usedPlatforms = {}; + for project in p.workspace.eachproject(workspace) do + if androidndk.isProjectSupported(project, false) then + for config in p.project.eachconfig(project) do + if androidndk.isConfigSupported(config, false) then + table.insertkeyed(usedBuildcfgs, config.buildcfg); + if config.platform ~= nil then + table.insertkeyed(usedPlatforms, config.platform); + end + end + end + end + end + + -- Application.mk. + p.generate(workspace, ".Application.mk", function(workspace) + -- Verify the correctness of the arguments, and set up the variables used for filtering in Application.mk. + androidndk.setupConfigConditionalMakeVariables(usedBuildcfgs, usedPlatforms); + + -- Gather Application.mk variable values for selected configurations and platforms. + -- As this is Application.mk, ABI filtering is not possible. + -- Therefore, selection of the required build configurations and platforms will work differently than in projects. + -- All selected build configurations and platforms must have the same values of settings written here. + -- For example, APP_OPTIM may be wrong with `PREMAKE_ANDROIDNDK_CONFIGURATIONS := Debug Release` if they have different `optimize`. + -- If one selected configuration-platform defines a value, while another doesn't care, pick from the one where it's explicitly set. + -- So, using if/if and ?= instead of if/elseif and :=, unlike in projects (where only configuration-platform pair is supposed to be chosen). + -- Most APP variables accept only one value - using ?= for them. + -- APP_ABI, however, accumulates the needed ABIs (in this case, for selected configurations and platforms), so using += for it. + + -- First, clear values that will be set - they might have been set externally while invoking ndk-build, override them. + -- Setting them to an empty value is not sufficient - `undefine` is required for ?= to work. + -- clear-vars is called for known APP variables before including Application.mk, which, as of NDK r23, defines the values as empty, however. + -- For this reason, if no value is provided, defining each of these variables as something (at least as empty). + p.w("undefine APP_ABI"); + p.w("undefine APP_DEBUG"); + p.w("undefine APP_OPTIM"); + p.w("undefine APP_PLATFORM"); + p.w("undefine PREMAKE_ANDROIDNDK_APP_STL_RUNTIME"); + p.w("undefine PREMAKE_ANDROIDNDK_APP_STL_LINKAGE"); + + -- Gather ABIs the projects are built for, for each configuration and platform for it. + -- Can't just collect all ABIs used by all platforms into a single APP_ABI assignment. + -- The reason is that some configurations may not even be targeting Android. + -- For instance, may only want ARM builds for Android, but x86 builds only for Windows. + -- If "all" is present in any platform-specific or platform-agnostic table, the whole platform must be treated as using "all". + local buildcfgsAbis = { + -- [1...] = [buildcfg] = { + -- platformSpecific = { + -- [1...] = [platform] = { + -- [1...] = [abi] = abi, + -- } + -- }, + -- platformAgnostic = { + -- [1...] = [abi] = abi, + -- }, + -- } + }; + local abiAgnosticConfigsUsed = false; + for project in p.workspace.eachproject(workspace) do + if androidndk.isProjectSupported(project, false) then + for config in p.project.eachconfig(project) do + if androidndk.isConfigSupported(config, false) then + local buildcfgAbis = buildcfgsAbis[config.buildcfg]; + if buildcfgAbis == nil then + buildcfgAbis = { platformSpecific = {}, platformAgnostic = {} }; + buildcfgsAbis[config.buildcfg] = buildcfgAbis; + end + local platformAbis; + if config.platform ~= nil then + platformAbis = buildcfgAbis.platformSpecific[config.platform]; + if platformAbis == nil then + platformAbis = {}; + buildcfgAbis.platformSpecific[config.platform] = platformAbis; + end + else + platformAbis = buildcfgAbis.platformAgnostic; + end + local abi = androidndk.getConfigAbi(config); + table.insertkeyed(platformAbis, abi); + if abi == androidndk.ABI_ALL then + abiAgnosticConfigsUsed = true; + end + end + end + end + end + + -- Gather the variables, and write the ABIs and variables. + for i, buildcfg in ipairs(usedBuildcfgs) do + local buildcfgConditionalOpen = false; + + local buildcfgAbis = buildcfgsAbis[buildcfg]; + -- Skip ABIs needed by platform-agnostic projects with this configuration from platform specializations. + -- ABIs needed by platform-agnostic projects will be added regardless of the platform. + local platformAgnosticAbis = nil; + local platformAgnosticAbisAll = false; + if buildcfgAbis ~= nil then + platformAgnosticAbis = buildcfgAbis.platformAgnostic; + platformAgnosticAbisAll = platformAgnosticAbis[androidndk.ABI_ALL] ~= nil; + end + + -- Gather and write platform-specific ABIs and platform specializations of APP variables. + for i2, platform in ipairs(usedPlatforms) do + -- Gather ABIs that any projects, for this buildcfg-platform pair, need to be built for. + -- Skip ABIs needed by platform-agnostic projects with this configuration from platform specializations. + -- ABIs needed by platform-agnostic projects will be added regardless of the platform. + local platformNewAbis = {}; + if buildcfgAbis ~= nil and not platformAgnosticAbisAll then + if buildcfgAbis ~= nil then + local platformAbisUnfiltered = buildcfgAbis.platformSpecific[platform]; + if platformAbisUnfiltered ~= nil then + for i3, abi in ipairs(platformAbisUnfiltered) do + if abi == androidndk.ABI_ALL then + platformNewAbis = { androidndk.ABI_ALL }; + break; + end + if platformAgnosticAbis == nil or platformAgnosticAbis[abi] == nil then + table.insert(platformNewAbis, abi); + end + end + -- Sort alphabetically for stable output. + table.sort(platformNewAbis); + end + end + end + -- Gather specializations of the APP variables for this platform. + local platformAppVariables = androidndk.gatherEscapedAppVariables(workspace, buildcfg, platform); + -- Write ABI additions and variable specializations for this platform. + if #platformNewAbis > 0 or #platformAppVariables > 0 then + if not buildcfgConditionalOpen then + buildcfgConditionalOpen = true; + p.push("ifneq ($(filter CONFIGURATION=%s,$(PREMAKE_ANDROIDNDK_CONFIGURATIONS_PREFIXED)),)", p.esc(buildcfg)); + end + p.push("ifneq ($(filter PLATFORM=%s,$(PREMAKE_ANDROIDNDK_PLATFORMS_PREFIXED)),)", p.esc(platform)); + androidndk.assignToVariablePostEsc("APP_ABI", platformNewAbis, false, nil, "+="); + for i3, variable in ipairs(platformAppVariables) do + androidndk.assignToVariablePostEsc(variable.variable, variable.value, false, nil, "?="); + end + -- Close the platform conditional. + p.pop("endif"); + end + end + + -- Write ABIs for platform-agnostic projects and APP variable values used when no platform specialization has been chosen. + local platformAgnosticAbisSorted = nil; + if platformAgnosticAbis ~= nil then + if platformAgnosticAbisAll then + platformAgnosticAbisSorted = { androidndk.ABI_ALL }; + else + platformAgnosticAbisSorted = table.arraycopy(platformAgnosticAbis); + -- Sort alphabetically for stable output. + table.sort(platformAgnosticAbisSorted); + end + end + local platformAgnosticAppVariables = androidndk.gatherEscapedAppVariables(workspace, buildcfg, nil); + if (platformAgnosticAbisSorted ~= nil and #platformAgnosticAbisSorted > 0) or #platformAgnosticAppVariables > 0 then + if not buildcfgConditionalOpen then + buildcfgConditionalOpen = true; + p.push("ifneq ($(filter CONFIGURATION=%s,$(PREMAKE_ANDROIDNDK_CONFIGURATIONS_PREFIXED)),)", p.esc(buildcfg)); + end + if platformAgnosticAbisSorted ~= nil then + androidndk.assignToVariablePostEsc("APP_ABI", platformAgnosticAbisSorted, false, nil, "+="); + end + for i2, variable in ipairs(platformAgnosticAppVariables) do + androidndk.assignToVariablePostEsc(variable.variable, variable.value, false, nil, "?="); + end + end + + if buildcfgConditionalOpen then + p.pop("endif"); + end + end + + -- Apply defaults if no configuration-specific overrides have been chosen, and normalize variables. + -- Also set variables that don't depend on the configuration. + + -- APP_ABI + -- Setting to "all" by default is not needed - empty already means `all`. + -- Also, it can happen only when no configuration with projects to build is selected. + -- Remove duplicates (sorting does that implicitly). + p.w("APP_ABI := $(sort $(APP_ABI))"); + -- If "all" was set for any configuration, and += of any other ABIs happened, make APP_ABI just "all". + if abiAgnosticConfigsUsed then + p.push("ifneq ($(filter " .. androidndk.ABI_ALL .. ",$(APP_ABI)),)"); + p.w("APP_ABI := " .. androidndk.ABI_ALL); + p.pop("endif"); + end + + -- APP_BUILD_SCRIPT + -- In the same directory, no need for p.filename followed by making it relative. + -- Also for this reason, no need for path.join, which may treat the name as an absolute path (for instance, if it begins with a $()\#-escaped #). + p.w("APP_BUILD_SCRIPT := %s", "$(call my-dir)/" .. p.esc(workspace.name .. ".wks.Android.mk")); + + -- APP_DEBUG and APP_OPTIM + -- Handling according to p.config.isDebugBuild and p.config.isOptimizedBuild logic (unoptimized by default, no symbols by default). + -- p.config.isDebugBuild always returns false (no symbols) for optimized builds. + -- For more information, see APP_DEBUG and APP_OPTIM gathering. + p.w("APP_OPTIM ?= debug"); + p.push("ifeq ($(APP_OPTIM),release)"); + p.w("APP_DEBUG := false"); + p.pop(); + p.push("else"); + p.w("APP_DEBUG ?= false"); + p.pop("endif"); + + -- APP_PLATFORM + -- Reverting the `undefine` because as of NDK r23, ndk-build sets all APP variables to empty values. + p.w("APP_PLATFORM ?="); + + -- APP_STL + -- The default for ndk-build is "none". Also, since the default for CMake is "c++_static", assuming static linkage by default. + p.w("PREMAKE_ANDROIDNDK_APP_STL_RUNTIME ?= none"); + p.w("PREMAKE_ANDROIDNDK_APP_STL_LINKAGE ?= _static"); + -- "none" and "system" don't have linkage suffixes. + p.push("ifneq ($(filter none system,$(PREMAKE_ANDROIDNDK_APP_STL_RUNTIME)),)"); + p.w("PREMAKE_ANDROIDNDK_APP_STL_LINKAGE :="); + p.pop("endif"); + p.w("APP_STL := $(PREMAKE_ANDROIDNDK_APP_STL_RUNTIME)$(PREMAKE_ANDROIDNDK_APP_STL_LINKAGE)"); + end); + + -- Android.mk. + p.generate(workspace, ".wks.Android.mk", function(workspace) + -- my-dir returns the directory containing the latest included file, not the directory with the current file. + -- Therefore, if project makefiles are written to other directories, calling it every time will result in incorrect paths. + p.w("PREMAKE_ANDROIDNDK_WORKSPACE_DIR := $(call my-dir)"); + + -- Verify the correctness of the arguments, and set up the variables used for filtering in Android.mk. + -- This needs to be in Android.mk even though it inherits Application.mk variables because with Gradle, Application.mk is optional. + androidndk.setupConfigConditionalMakeVariables(usedBuildcfgs, usedPlatforms); + + -- Setup post-expansion escaping in text passed to command execution via the shell. + -- In some variables - LOCAL_CFLAGS/LOCAL_LDFLAGS, LOCAL_C_INCLUDES, LOCAL_MODULE_FILENAME - path requirements are pretty relaxed. + -- On Linux, file names may contain " or \, but they need to be escaped with \ prefix as otherwise they're interpreted by the shell. + -- On Windows, " is not allowed in file names, but a single \ is treated as a path separator - no escaping required. + -- Function for calling inside quoted arguments (to be followed by the function for a list of arguments). + p.w("%s = $(subst \",\\\",$(subst \\,\\\\,$(1)))", androidndk.shellEscapePreQuotesMakeCall); + -- Function for calling for lists of quoted arguments. + local escapeFunction = { androidndk.shellEscapePostQuotesMakeCall, "_NO_BRACKETS = " }; + local bracketsEncountered = 0; + for i = #androidndk.shellEscapedCharactersPostQuotes, 1, -1 do + local character = androidndk.shellEscapedCharactersPostQuotes[i]; + if character == "(" or character == ")" then + -- Brackets must be used in a ${} call, not $(). + bracketsEncountered = bracketsEncountered + 1; + else + if character == "#" then + -- Escape the comment character similar to the way androidndk.esc does that, breaking the dependency on the number of backslashes. + character = androidndk.commentEscapeString; + elseif character == "$" then + character = "$$"; + end + table.insert(escapeFunction, "$(subst "); + table.insert(escapeFunction, character); + table.insert(escapeFunction, ",\\"); + table.insert(escapeFunction, character); + table.insert(escapeFunction, ","); + end + end + table.insert(escapeFunction, "$(1)"); + table.insert(escapeFunction, string.rep(")", #androidndk.shellEscapedCharactersPostQuotes - bracketsEncountered)); + p.w(table.concat(escapeFunction)); + -- Escape the brackets as well. + p.w( + "%s = ${subst ),\\),${subst (,\\(,${call %s_NO_BRACKETS,${1}}}}", + androidndk.shellEscapePostQuotesMakeCall, + androidndk.shellEscapePostQuotesMakeCall); + -- Function for raw text without arguments containing whitespaces that need to be wrapped in true, unescaped quotes. + p.w( + "%s = $(call %s,$(call %s,$(1)))", + androidndk.shellEscapeMakeCall, + androidndk.shellEscapePostQuotesMakeCall, + androidndk.shellEscapePreQuotesMakeCall); + -- Function for LOCAL_MODULE_FILENAME. + -- : must be \-escaped on Linux. + -- It's a drive letter separator on Windows, however, so it's disallowed in LOCAL_MODULE_FILENAME there (it's a name, not a path). + -- Therefore, there's no need to handle it separately on Windows and Linux - just \-escape : unconditionally. + p.w("%s = $(subst :,\\:,$(call %s,$(1)))", androidndk.shellEscapeModuleFileNameMakeCall, androidndk.shellEscapeMakeCall); + -- Function for LOCAL_SRC_FILES and LOCAL_PCH. + -- : must be \-escaped on Linux, but cannot be escaped on non-Cygwin Windows, and is a drive letter separator there supported directly. + p.push("ifeq ($(HOST_OS),windows)"); + p.w("%s = $(call %s,$(1))", androidndk.shellEscapeSrcFilesMakeCall, androidndk.shellEscapeMakeCall); + p.pop(); + p.push("else"); + p.w("%s = $(subst :,\\:,$(call %s,$(1)))", androidndk.shellEscapeSrcFilesMakeCall, androidndk.shellEscapeMakeCall); + p.pop("endif"); + + -- Include makefiles of all projects. + -- Same path logic as in p.generate. + local workspaceDirectory = path.getdirectory(p.filename(workspace, ".wks.Android.mk")); + for project in p.workspace.eachproject(workspace) do + if androidndk.isProjectSupported(project, false) then + p.w( + "include %s", + androidndk.preventWhitespaceTrimming( + path.join( + "$(PREMAKE_ANDROIDNDK_WORKSPACE_DIR)", + p.esc(path.getrelative(workspaceDirectory, p.filename(project, ".prj.Android.mk")))))); + end + end + end); +end diff --git a/src_rebuild/tools/font_tool/font_tool_main.cpp b/src_rebuild/tools/font_tool/font_tool_main.cpp new file mode 100644 index 00000000..407d2916 --- /dev/null +++ b/src_rebuild/tools/font_tool/font_tool_main.cpp @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" +#include "targa.h" + +#include "hqfont.h" + +struct FN2RangeInfo +{ + OUT_FN2RANGE hdr; + OUT_FN2INFO* chars; +}; + +static FN2RangeInfo fontRanges[4]; +static int fontRangeCount = 0; + +void Usage() +{ + printf("example: FontTool -i -o \n\nAdditional arguments:"); + printf("\t-r : add range of characters"); +} + +int main(int argc, char** argv) +{ + if (argc < 2) + { + Usage(); + return 0; + } + + { + FN2RangeInfo& firstRange = fontRanges[0]; + + firstRange.hdr.start = 32; + firstRange.hdr.count = 224; + firstRange.chars = new OUT_FN2INFO[firstRange.hdr.count]; + ++fontRangeCount; + } + + const char* inputFilename = nullptr; + const char* outpitFilename = nullptr; + + for (int i = 0; i < argc; ++i) + { + if (!strcmp(argv[i], "-i") && i + 1 < argc) + { + inputFilename = argv[i+1]; + } + else if (!strcmp(argv[i], "-o") && i + 1 < argc) + { + outpitFilename = argv[i + 1]; + } + else if (!strcmp(argv[i], "-r") && i + 2 < argc) + { + FN2RangeInfo& newRange = fontRanges[fontRangeCount]; + + newRange.hdr.start = atoi(argv[i + 1]); + newRange.hdr.count = atoi(argv[i + 2]); + newRange.chars = new OUT_FN2INFO[newRange.hdr.count]; + ++fontRangeCount; + } + } + + if (!inputFilename) + { + Usage(); + return 0; + } + + if (!outpitFilename) + { + Usage(); + return 0; + } + + FILE* fp = fopen(inputFilename, "rb"); + if (!fp) + { + printf("Cannot open %s\n", inputFilename); + return -1; + } + + // read whole file + fseek(fp, 0, SEEK_END); + const long size = ftell(fp); + fseek(fp, 0, SEEK_SET); + u_char* data = (u_char*)malloc(size); + fread(data, 1, size, fp); + fclose(fp); + + // gen font + u_char* tmpBitmap = (u_char*)malloc(HIRES_FONT_SIZE_W * HIRES_FONT_SIZE_H); + u_int* bitmapRGBA = (u_int*)malloc(HIRES_FONT_SIZE_W * HIRES_FONT_SIZE_H * 4); + + stbtt_pack_context pc; + stbtt_PackBegin(&pc, tmpBitmap, HIRES_FONT_SIZE_W, HIRES_FONT_SIZE_H, 0, 2, NULL); + stbtt_PackSetOversampling(&pc, 1, 1); + stbtt_PackSetSkipMissingCodepoints(&pc, 1); + + for (int i = 0; i < fontRangeCount; ++i) + { + FN2RangeInfo& range = fontRanges[i]; + stbtt_PackFontRange(&pc, data, 0, 65.0f, range.hdr.start, range.hdr.count, (stbtt_packedchar*)range.chars); + } + + stbtt_PackEnd(&pc); + + for (int x = 0; x < HIRES_FONT_SIZE_W; ++x) + { + for (int y = 0; y < HIRES_FONT_SIZE_H; ++y) + { + bitmapRGBA[x + y * HIRES_FONT_SIZE_W] = tmpBitmap[x + y * HIRES_FONT_SIZE_W] << 24 | 0xffffff; + } + } + + { + char tgaFileName[256]; + strcpy(tgaFileName, outpitFilename); + strcat(tgaFileName, ".tga"); + + SaveTGAImage(tgaFileName, (u_char*)bitmapRGBA, HIRES_FONT_SIZE_W, HIRES_FONT_SIZE_H, 32); + } + + { + char fntFileName[256]; + strcpy(fntFileName, outpitFilename); + strcat(fntFileName, ".fn2"); + + FILE* fntFp = fopen(fntFileName, "wb"); + if (fntFp) + { + OUT_FN2HEADER fn2hdr; + fn2hdr.version = FN2_VERSION; + fn2hdr.range_count = fontRangeCount; + fwrite(&fn2hdr, sizeof(fn2hdr), 1, fntFp); + + for (int i = 0; i < fontRangeCount; ++i) + { + FN2RangeInfo& range = fontRanges[i]; + fwrite(&range.hdr, sizeof(OUT_FN2RANGE), 1, fntFp); + fwrite(range.chars, sizeof(OUT_FN2INFO), range.hdr.count, fntFp); + + delete[] range.chars; + } + } + } + + free(bitmapRGBA); + free(tmpBitmap); + free(data); +} \ No newline at end of file diff --git a/src_rebuild/DebugOverlay.cpp b/src_rebuild/utils/DebugOverlay.cpp similarity index 95% rename from src_rebuild/DebugOverlay.cpp rename to src_rebuild/utils/DebugOverlay.cpp index 0797b523..2c88b5b1 100644 --- a/src_rebuild/DebugOverlay.cpp +++ b/src_rebuild/utils/DebugOverlay.cpp @@ -1,21 +1,19 @@ -#include "Game/driver2.h" - -#include "Game/C/mission.h" -#include "Game/C/convert.h" -#include "Game/C/camera.h" -#include "Game/C/dr2roads.h" -#include "Game/C/system.h" -#include "Game/C/pres.h" -#include "Game/C/spool.h" -#include "Game/C/cars.h" -#include "Game/C/draw.h" -#include "Game/C/players.h" -#include "Game/C/glaunch.h" - -#include +#include "driver2.h" +#include "C/mission.h" +#include "C/convert.h" +#include "C/camera.h" +#include "C/dr2roads.h" +#include "C/system.h" +#include "C/pres.h" +#include "C/spool.h" +#include "C/cars.h" +#include "C/draw.h" +#include "C/players.h" +#include "C/glaunch.h" #include "C/felony.h" +#include int gDisplayDrawStats = 0; @@ -245,7 +243,7 @@ void Debug_Line2D(SXYPAIR& pointA, SXYPAIR& pointB, CVECTOR& color) line->g0 = color.g; line->b0 = color.b; -#if defined(USE_PGXP) && defined(USE_EXTENDED_PRIM_POINTERS) +#if USE_PGXP && USE_EXTENDED_PRIM_POINTERS line->pgxp_index = 0xFFFF; #endif diff --git a/src_rebuild/utils/hqfont.h b/src_rebuild/utils/hqfont.h new file mode 100644 index 00000000..0c125096 --- /dev/null +++ b/src_rebuild/utils/hqfont.h @@ -0,0 +1,27 @@ +#ifndef HQFONT_H +#define HQFONT_H + +#define FN2_VERSION 1 +#define HIRES_FONT_SIZE_W 768 +#define HIRES_FONT_SIZE_H 768 + +struct OUT_FN2INFO +{ + u_short x0, y0, x1, y1; + float xoff, yoff, xadvance; + float xoff2, yoff2; +}; + +struct OUT_FN2RANGE +{ + u_short start; + u_short count; +}; + +struct OUT_FN2HEADER +{ + u_short version; + u_short range_count; +}; + +#endif // HQFONT_H \ No newline at end of file diff --git a/src_rebuild/utils/targa.cpp b/src_rebuild/utils/targa.cpp index 82dbacf6..ffecd628 100644 --- a/src_rebuild/utils/targa.cpp +++ b/src_rebuild/utils/targa.cpp @@ -177,5 +177,61 @@ bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, free(tempBuffer); free(fBuffer); + return true; +} + +bool SaveTGAImage(const char* filename, u_char* data, int width, int height, int bpp) +{ + TGAHeader tgaHeader; + + // Initialize the Targa header + tgaHeader.descriptionlen = 0; + tgaHeader.cmaptype = 0; + tgaHeader.imagetype = 2; + tgaHeader.cmapstart = 0; + tgaHeader.cmapentries = 0; + tgaHeader.cmapbits = 0; + tgaHeader.xoffset = 0; + tgaHeader.yoffset = 0; + tgaHeader.width = width; + tgaHeader.height = height; + tgaHeader.bpp = bpp; + tgaHeader.attrib = 0; + + int imageSize = width * height * (bpp / 8); + + FILE* fp = fopen(filename, "wb"); + if (!fp) + return false; + + // Write the header + fwrite(&tgaHeader, sizeof(TGAHeader), 1, fp); + + // Write the image data + u_char* src = data + (bpp / 8) * width * (height - 1); + + switch (bpp) + { + case 32: + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + u_char pixel[4]; + pixel[0] = src[2]; + pixel[1] = src[1]; + pixel[2] = src[0]; + pixel[3] = src[3]; + + fwrite(pixel, sizeof(pixel), 1, fp); + src += 4; + } + src -= 8 * width; + } + break; + } + + fclose(fp); + return true; } \ No newline at end of file diff --git a/src_rebuild/utils/targa.h b/src_rebuild/utils/targa.h index 0183663a..8190f903 100644 --- a/src_rebuild/utils/targa.h +++ b/src_rebuild/utils/targa.h @@ -24,5 +24,6 @@ struct TGAHeader #pragma pack (pop) bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, int& bpp); +bool SaveTGAImage(const char* filename, u_char* data, int width, int height, int bpp); #endif // TARGA_H \ No newline at end of file diff --git a/src_rebuild/utils/video_source/VideoPlayer.cpp b/src_rebuild/utils/video_source/VideoPlayer.cpp index 4f0b2b0d..e4210558 100644 --- a/src_rebuild/utils/video_source/VideoPlayer.cpp +++ b/src_rebuild/utils/video_source/VideoPlayer.cpp @@ -180,7 +180,7 @@ void SetupMovieRectangle(int image_w, int image_h) u_char r = 1; u_char b = 1; -#ifdef USE_PGXP +#if USE_PGXP GR_SetViewPort(0, 0, windowWidth, windowHeight); GrVertex blit_vertices[] =