
trigger:
  paths:
    include:
      - '*'
    exclude:
      - '.azure/azure-pipelines-mac.yml'
      - '.azure/azure-pipelines-vcpkg.yml'
      - '.azure/azure-pipelines-win.yml'
      - '.circleci/config.yml'
      - '.github/workflows/linux.yml'
      - '.github/workflows/sonarcloud.yml'
      - '.cirrus.yml'
      - 'README.md'

parameters:
- name: UseCache
  displayName: Use Dependency Cache
  type: boolean
  default: true
  values:
  - true
  - false

schedules:
- cron: "0 0 * * 1"
  displayName: 'Weekly Monday Midnight build without caching'
  branches:
    include:
    - master
  always: true

variables:
  ICONV_VERSION: '1.17'
  # Use recent master libffi, because 3.3 is broken: checking host system type... Invalid configuration `arm-none-linux-eabi': machine `arm-none-linux' not recognized
  FFI_VERSION: '3.4.2'
  GETTEXT_VERSION: '0.21'
  GLIB_VERSION: '2.72'
  GLIB_EXTRAVERSION: '3'
  OBOE_VERSION: '1.6.1'
  SNDFILE_VERSION: '1.1.0'
  INSTPATCH_VERSION: '1.1.6'
  VORBIS_VERSION: '1.3.7'
  OGG_VERSION: '1.3.5'
  OPUS_VERSION: '1.3.1'
  FLAC_VERSION: '1.3.4'
  PCRE_VERSION: '8.45'

  # Android NDK sources and standalone toolchain is put here
  DEV: '$(System.DefaultWorkingDirectory)/android-build-root'

  # This is a symlink pointing to the real Android NDK
  # Must be the same as $ANDROID_NDK_HOME see:
  # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-README.md
  # We cannot use $ANDROID_NDK_HOME because this is an environment variable, but here, we need a compile-time constant.
  NDK: '/usr/local/lib/android/sdk/ndk/25.2.9519653'

  # All the built binaries, libs and their headers will be installed here
  PREFIX: '$(DEV)/opt/android'

  # Prevent installing to lib64/
  # This becomes important, if you would build on e.g. openSUSE
  LIBPATH0: '$(PREFIX)/lib'

  # The path of standalone NDK toolchain
  # Refer to https://developer.android.com/ndk/guides/standalone_toolchain.html
  NDK_TOOLCHAIN: '$(NDK)/toolchains/llvm/prebuilt/linux-x86_64/'

  # Don't mix up .pc files from your host and build target
  PKG_CONFIG_PATH: '$(LIBPATH0)/pkgconfig'
  # setting PKG_CONFIG_PATH alone does not seem to be enough to avoid mixing up with the host, also set PKG_CONFIG_LIBDIR
  PKG_CONFIG_LIBDIR: '$(PKG_CONFIG_PATH)'

  # Set Android target API level
  # when compiling with clang use at least 28 as this makes sure that android provides the posix_spawn functions, so the compilation of gettext will (should) work out of the box
  # it's probably a bug of gettext, if posix_spawn is not available it replaces it with its own implementation. Autotools of gettext set HAVE_POSIX_SPAWN==0 (which is correct) but for some reason REPLACE_POSIX_SPAWN==0 (which is wrong, as it should be 1).
  # 
  # NOTE: API 24 is required because it provides fseeko() and ftello() required by libflac
  ANDROID_API: '24'

  # Tell configure what flags Android requires.
  # Turn Wimplicit-function-declaration into errors. Else autotools will be fooled when checking for available functions (that in fact are NOT available) and compilation will fail later on.
  CFLAGS: "-fPIE -fPIC -I$(PREFIX)/include --sysroot=$(NDK_TOOLCHAIN)/sysroot -I$(NDK_TOOLCHAIN)/sysroot/usr/include -Werror=implicit-function-declaration"
  CXXFLAGS: $(CFLAGS)
  CPPFLAGS: $(CXXFLAGS)
  DEBIAN_FRONTEND: 'noninteractive'

  ARTIFACT_NAME: 'fluidsynth-android$(ANDROID_API)'

jobs:
- job: Android
  strategy:
    matrix:
      ARM:
        ARCH: 'arm'
        ANDROID_ARCH: 'armv7a'
        ANDROID_ABI_CMAKE: 'armeabi-v7a'
        ANDROID_TARGET_ABI: "eabi"
        ANDROID_ABI_MESON: 'arm'
        # the --target to be used by autotools
        AUTOTOOLS_TARGET: "$(ARCH)-linux-android$(ANDROID_TARGET_ABI)"
      AARCH64:
        ARCH: 'aarch64'
        ANDROID_ARCH: 'aarch64'
        ANDROID_ABI_CMAKE: 'arm64-v8a'
        ANDROID_TARGET_ABI:
        ANDROID_ABI_MESON: 'aarch64'
        AUTOTOOLS_TARGET: "$(ARCH)-none-linux-android"
      i686:
        ARCH: 'i686'
        ANDROID_ARCH: 'i686'
        ANDROID_ABI_CMAKE: 'x86'
        ANDROID_TARGET_ABI:
        ANDROID_ABI_MESON: 'x86'
        AUTOTOOLS_TARGET: "$(ARCH)-pc-linux-android"
      x86_64:
        ARCH: 'x86_64'
        ANDROID_ARCH: 'x86_64'
        ANDROID_ABI_CMAKE: 'x86_64'
        ANDROID_TARGET_ABI:
        ANDROID_ABI_MESON: 'x86_64'
        AUTOTOOLS_TARGET: "$(ARCH)-pc-linux-android"

  pool:
    vmImage: 'ubuntu-22.04'

  steps:
    - script: |
        set -ex
        mkdir -p $(DEV)
      displayName: 'mkdir $(DEV)'

    - script: |
        sudo apt-get update -y
      displayName: 'Update apt'

    - script: |
        set -ex
        sudo -E apt-get -y --no-install-suggests --no-install-recommends install wget tar bzip2 xz-utils ca-certificates
      displayName: 'apt-get install wget tar'

    - script: |
        set -ex

        wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-${ICONV_VERSION}.tar.gz
        tar zxf libiconv-${ICONV_VERSION}.tar.gz

        wget -O libffi-${FFI_VERSION}.tar.gz https://github.com/libffi/libffi/releases/download/v${FFI_VERSION}/libffi-${FFI_VERSION}.tar.gz
        tar zxf libffi-${FFI_VERSION}.tar.gz

        wget http://ftp.gnu.org/pub/gnu/gettext/gettext-${GETTEXT_VERSION}.tar.gz
        tar zxf gettext-${GETTEXT_VERSION}.tar.gz

        wget http://ftp.gnome.org/pub/gnome/sources/glib/${GLIB_VERSION}/glib-${GLIB_VERSION}.${GLIB_EXTRAVERSION}.tar.xz
        tar xf glib-${GLIB_VERSION}.${GLIB_EXTRAVERSION}.tar.xz

        wget -O oboe-${OBOE_VERSION}.tar.gz https://github.com/google/oboe/archive/${OBOE_VERSION}.tar.gz
        tar zxf oboe-${OBOE_VERSION}.tar.gz

        wget https://github.com/libsndfile/libsndfile/releases/download/${SNDFILE_VERSION}/libsndfile-${SNDFILE_VERSION}.tar.xz
        tar xf libsndfile-${SNDFILE_VERSION}.tar.xz

        wget -O libinstpatch-${INSTPATCH_VERSION}.tar.gz https://github.com/swami/libinstpatch/archive/refs/tags/v${INSTPATCH_VERSION}.tar.gz
        tar zxf libinstpatch-${INSTPATCH_VERSION}.tar.gz

        wget https://github.com/xiph/vorbis/releases/download/v${VORBIS_VERSION}/libvorbis-${VORBIS_VERSION}.tar.gz
        tar zxf libvorbis-${VORBIS_VERSION}.tar.gz

        wget https://github.com/xiph/ogg/releases/download/v${OGG_VERSION}/libogg-${OGG_VERSION}.tar.gz
        tar zxf libogg-${OGG_VERSION}.tar.gz

        wget -O flac-${FLAC_VERSION}.tar.gz https://github.com/xiph/flac/archive/refs/tags/${FLAC_VERSION}.tar.gz
        tar xf flac-${FLAC_VERSION}.tar.gz

        wget -O opus-${OPUS_VERSION}.tar.gz https://github.com/xiph/opus/archive/refs/tags/v${OPUS_VERSION}.tar.gz
        tar xf opus-${OPUS_VERSION}.tar.gz

        wget -O pcre-${PCRE_VERSION}.tar.bz2 https://sourceforge.net/projects/pcre/files/pcre/${PCRE_VERSION}/pcre-${PCRE_VERSION}.tar.bz2/download
        tar jxf pcre-${PCRE_VERSION}.tar.bz2
        cd pcre-${PCRE_VERSION}
        # CMake checks for existence of strtoq() using the C compiler - and yes, it does exist!
        # Later on, it's actually used by the C++ compiler, where it does not exist.
        # Rename the function so CMake won't find it.
        sed -i 's/strtoq/strtoqqqq/g' CMakeLists.txt
      displayName: 'Download Dependencies'
      workingDirectory: $(DEV)

    - task: Cache@2
      inputs:
        key: '$(ARCH) | "$(NDK)" | $(DEV)/*.tar.*'
        path: '$(PREFIX)'
        cacheHitVar: 'CACHE_RESTORED'
      displayName: 'Cache fluidsynth dependency libraries'
      condition: and(not(in(variables['Build.Reason'], 'Schedule')), ${{ parameters.useCache }})

    - script: |
        set -ex

        sudo apt remove --purge --auto-remove cmake
        wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | sudo tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null
        sudo apt-add-repository 'deb https://apt.kitware.com/ubuntu/ focal main'
      displayName: 'Use recent CMake Version'
      condition: and(succeeded(), ne(variables.CACHE_RESTORED, 'true'))
      enabled: 'false'

    - script: |
        set -ex
        sudo -E apt-get -y --no-install-suggests --no-install-recommends install gettext cmake zlib1g-dev autogen automake autoconf libtool pkg-config autotools-dev build-essential meson ninja-build python3-distutils
      displayName: 'apt-get install build-tools'
      condition: and(succeeded(), ne(variables.CACHE_RESTORED, 'true'))

    - script: |
        set -e

        # The cross-compile toolchain we use
        export ANDROID_TARGET=${ARCH}-linux-android${ANDROID_TARGET_ABI}
        echo "##vso[task.setvariable variable=ANDROID_TARGET]$ANDROID_TARGET"

        export ANDROID_TARGET_API=${ANDROID_ARCH}-linux-android${ANDROID_TARGET_ABI}${ANDROID_API}
        echo "##vso[task.setvariable variable=ANDROID_TARGET_API]$ANDROID_TARGET_API"

        # Add the standalone toolchain to the search path.
        export PATH=$PATH:${PREFIX}/bin:${PREFIX}/lib:${PREFIX}/include:${NDK_TOOLCHAIN}/bin
        echo "##vso[task.setvariable variable=PATH]$PATH"
        
        LIBPATH1=$(NDK_TOOLCHAIN)/sysroot/usr/lib
        LIBPATH2=$(NDK_TOOLCHAIN)/sysroot/usr/lib/$(ARCH)-linux-android$(ANDROID_TARGET_ABI)/$(ANDROID_API)
        LIBPATH3=$(NDK_TOOLCHAIN)/sysroot/usr/lib/$(ARCH)-linux-android$(ANDROID_TARGET_ABI)

        export LDFLAGS="-pie -Wl,-rpath-link=${LIBPATH1} -L${LIBPATH1} -Wl,-rpath-link=${LIBPATH2} -L${LIBPATH2} -Wl,-rpath-link=${LIBPATH3} -L${LIBPATH3} -Wl,-rpath-link=${LIBPATH0} -L${LIBPATH0}"
        echo "##vso[task.setvariable variable=LDFLAGS]$LDFLAGS"

        # Tell configure what tools to use.
        export AR=llvm-ar
        echo "##vso[task.setvariable variable=AR]$AR"
        export AS=${ANDROID_TARGET_API}-clang
        echo "##vso[task.setvariable variable=AS]$AS"
        export CC=${ANDROID_TARGET_API}-clang
        echo "##vso[task.setvariable variable=CC]$CC"
        export CXX=${ANDROID_TARGET_API}-clang++
        echo "##vso[task.setvariable variable=CXX]$CXX"
        export LD=ld.lld
        echo "##vso[task.setvariable variable=LD]$LD"
        export STRIP=llvm-strip
        echo "##vso[task.setvariable variable=STRIP]$STRIP"
        export RANLIB=llvm-ranlib
        echo "##vso[task.setvariable variable=RANLIB]$RANLIB"

      displayName: 'Set environment variables'

    - script: |
        set -ex

        pushd libiconv-${ICONV_VERSION}
        ./configure \
          --host=${AUTOTOOLS_TARGET} \
          --prefix=${PREFIX} \
          --libdir=${LIBPATH0} \
          --disable-rpath \
          --enable-static \
          --disable-shared \
          --with-pic \
          --disable-maintainer-mode \
          --disable-silent-rules \
          --disable-gtk-doc \
          --disable-introspection \
          --disable-nls
        make -j$((`nproc`+1))
        make install
        popd

      displayName: 'Compile libiconv'
      workingDirectory: $(DEV)
      condition: and(succeeded(), ne(variables.CACHE_RESTORED, 'true'))

    - script: |
        set -ex
        pushd libiconv-${ICONV_VERSION}
        cat config.log
      displayName: 'libiconv config.log'
      workingDirectory: $(DEV)
      condition: and(failed(), ne(variables.CACHE_RESTORED, 'true'))

    - script: |
        set -ex

        pushd libffi-${FFI_VERSION}
        NOCONFIGURE=true autoreconf -v -i
        # install headers into the conventional ${PREFIX}/include rather than ${PREFIX}/lib/libffi-3.2.1/include.
        #sed -e '/^includesdir/ s/$(libdir).*$/$(includedir)/' -i include/Makefile.in
        #sed -e '/^includedir/ s/=.*$/=@includedir@/' -e 's/^Cflags: -I${includedir}/Cflags:/' -i libffi.pc.in
        ./configure --host=${AUTOTOOLS_TARGET} --prefix=${PREFIX} --libdir=${LIBPATH0} --enable-static --disable-shared
        make -j$((`nproc`+1))
        make install
        popd
      displayName: 'Compile libffi'
      workingDirectory: $(DEV)
      condition: and(succeeded(), ne(variables.CACHE_RESTORED, 'true'))

    - script: |
        set -ex

        pushd gettext-${GETTEXT_VERSION}
        ./configure \
          --host=${AUTOTOOLS_TARGET} \
          --prefix=${PREFIX} \
          --libdir=${LIBPATH0} \
          --disable-rpath \
          --disable-libasprintf \
          --disable-java \
          --disable-native-java \
          --disable-openmp \
          --disable-curses \
          --enable-static \
          --disable-shared \
          --with-pic  \
          --disable-maintainer-mode \
          --disable-silent-rules \
          --disable-gtk-doc \
          --disable-introspection
        make -j$((`nproc`+1))
        make install
        popd
      displayName: 'Compile gettext'
      workingDirectory: $(DEV)
      condition: and(succeeded(), ne(variables.CACHE_RESTORED, 'true'))

    - template: cmake-android.yml
      parameters:
        sourceDir: 'pcre-$(PCRE_VERSION)'
        cmakeArgs: '-DPCRE_SUPPORT_UNICODE_PROPERTIES=1 -DPCRE_SUPPORT_UTF=1 -DPCRE_BUILD_PCRECPP=0 -DPCRE_BUILD_TESTS=0'

    - script: |
        set -ex
        export PKGCFG=`which pkg-config`
        pushd glib-${GLIB_VERSION}.${GLIB_EXTRAVERSION}

        cat << EOF > cross_file.ini
        [host_machine]
        system = 'android'
        cpu_family = '${ANDROID_ABI_MESON}'
        cpu = '${ANDROID_ARCH}'
        endian = 'little'

        [binaries]
        c = '${NDK_TOOLCHAIN}/bin/${CC}'
        cpp = '${NDK_TOOLCHAIN}/bin/${CXX}'
        ar = '${NDK_TOOLCHAIN}/bin/${AR}'
        as = '${NDK_TOOLCHAIN}/bin/${AS}'
        ld = '${NDK_TOOLCHAIN}/bin/${LD}'
        strip = '${NDK_TOOLCHAIN}/bin/${STRIP}'
        ranlib = '${NDK_TOOLCHAIN}/bin/${RANLIB}'
        pkgconfig = '${PKGCFG}'

        [properties]
        prefix = '${PREFIX}'
        c_args = '${CFLAGS}'
        cpp_args = '${CXXFLAGS}'
        pkg_config_libdir = '${PKG_CONFIG_LIBDIR}'
        c_link_args = '${LDFLAGS}'

        [project options]
        libmount = 'disabled'
        xattr = false
        selinux = 'disabled'
        nls = 'disabled'
        glib_debug = 'disabled'
        glib_assert = false
        glib_checks = false
        libelf = 'disabled'
        EOF

        cat cross_file.ini

        # When CC and CXX are set, then meson detects them as host compiler (not cross compiler),
        # so they tries to run arm binaries in x86. That's why sanity check is failing, and that's
        # why we have to use env -i.
        # And we must explicitly set PKG_CONFIG_LIBDIR, because pkg_config_libdir is only recognized by meson >= 0.54
        env -i bash -c "export PATH && export PKG_CONFIG_LIBDIR=$(PKG_CONFIG_LIBDIR) && meson setup build --cross-file cross_file.ini --prefix=$(PREFIX)"
        ninja -C build
        ninja -C build install
        popd
      displayName: 'Compile glib (meson)'
      workingDirectory: $(DEV)
      condition: and(succeeded(), ne(variables.CACHE_RESTORED, 'true'))

    - script: |
        pushd glib-${GLIB_VERSION}.${GLIB_EXTRAVERSION}
        ls -la build
        cat build/meson-logs/meson-log.txt
        popd
      displayName: 'Meson LOG'
      workingDirectory: $(DEV)
      condition: and(succeededOrFailed(), ne(variables.CACHE_RESTORED, 'true'))

    - template: cmake-android.yml
      parameters:
        sourceDir: 'libogg-$(OGG_VERSION)'
        cmakeArgs: '-DINSTALL_DOCS=0'

    - template: cmake-android.yml
      parameters:
        sourceDir: 'libvorbis-$(VORBIS_VERSION)'

    # flac uses c99 macros, but doesnt specify a standard, so we need to do it explicitly.
    # On i686, they invoke yasm with -fstack-protector-strong flag... turn off asm optimizations.
    - template: cmake-android.yml
      parameters:
        sourceDir: 'flac-$(FLAC_VERSION)'
        cmakeArgs: '-DCMAKE_C_STANDARD=99 -DCMAKE_C_STANDARD_REQUIRED=1 -DWITH_ASM=0 -DBUILD_CXXLIBS=0 -DBUILD_PROGRAMS=0 -DBUILD_EXAMPLES=0 -DBUILD_DOCS=0 -DINSTALL_MANPAGES=0'

    # another broken xiph project that doesn't specify the C standard and keeps complaining of you don't have C99
    - template: cmake-android.yml
      parameters:
        sourceDir: 'opus-$(OPUS_VERSION)'
        cmakeArgs: '-DBUILD_PROGRAMS=0 -DOPUS_MAY_HAVE_NEON=1 -DCMAKE_C_STANDARD=99 -DCMAKE_C_STANDARD_REQUIRED=1'

    - template: cmake-android.yml
      parameters:
        sourceDir: 'libsndfile-$(SNDFILE_VERSION)'
        cmakeArgs: '-DBUILD_PROGRAMS=0 -DBUILD_EXAMPLES=0'

    - template: cmake-android.yml
      parameters:
        sourceDir: 'oboe-$(OBOE_VERSION)'
        installCommand: 'cp liboboe.* ${PREFIX}/lib/ && cp -ur ../include/oboe ${PREFIX}/include'
  
    - script: |
        set -ex

        # create a custom pkgconfig file for oboe to allow fluidsynth to find it
        cat << EOF > ${PKG_CONFIG_PATH}/oboe-1.0.pc
        prefix=${PREFIX}
        exec_prefix=\${prefix}
        libdir=\${prefix}/lib
        includedir=\${prefix}/include

        Name: Oboe
        Description: Oboe library
        Version: ${OBOE_VERSION}
        Libs: -L\${libdir} -loboe -landroid -llog
        Cflags: -I\${includedir}
        EOF

        cat ${PKG_CONFIG_PATH}/oboe-1.0.pc

      displayName: 'Create fake oboe.pc'
      workingDirectory: $(DEV)
      condition: and(succeeded(), ne(variables.CACHE_RESTORED, 'true'))

    - template: cmake-android.yml
      parameters:
        sourceDir: 'libinstpatch-$(INSTPATCH_VERSION)'

    # finally, compile fluidsynth
    - template: cmake-android.yml
      parameters:
        workDir: '$(System.DefaultWorkingDirectory)'
        sourceDir: '.'
        condition: succeeded()
        cmakeArgs: '-Denable-opensles=1 -Denable-floats=1 -Denable-oboe=1 -Denable-dbus=0 -Denable-oss=0'
        installCommand: ''

    - script: |
        set -x
        export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(PREFIX)/lib:$(NDK_TOOLCHAIN)/sysroot/usr/lib/$(ARCH)-linux-android$(ANDROID_TARGET_ABI)/$(ANDROID_API)
        pushd build
        make -j$((`nproc`+1)) check
        ldd test/test_sample_cache
        popd
      displayName: 'Execute fluidsynth unit test'
      condition: and(succeeded(), in(variables['ARCH'], 'x86_64', 'i686'))
      enabled: 'false'

    - script: |
        set -ex
        pushd build
        make install
        popd
      displayName: 'Install fluidsynth'

    - template: cmake-android.yml
      parameters:
        workDir: '$(System.DefaultWorkingDirectory)/doc/android/fluidsynth-assetloader/'
        sourceDir: '.'
        condition: succeeded()
        installCommand: 'cp *.so ${PREFIX}/lib/'

    - script: |
        ls -Rg $(PREFIX)
      displayName: 'Show cross-compiled files in $(PREFIX)'
      condition: or(succeeded(), failed())

    - script: |
        set -ex
        # use ANDROID_ABI_CMAKE so libs can be simply copied to the archive contents in src/main/jniLibs
        mkdir -p $(Build.ArtifactStagingDirectory)/lib/$(ANDROID_ABI_CMAKE)
        cd $(Build.ArtifactStagingDirectory)/lib/$(ANDROID_ABI_CMAKE)
        cp -LR $(PREFIX)/lib/* .
        ls -Rg .
        rm -rf *.dll *.alias gettext/ libtextstyle.* *.a *.la
        rm -f *.so.*
        mkdir -p $(Build.ArtifactStagingDirectory)/include
        cd $(Build.ArtifactStagingDirectory)/include
        cp -a $(PREFIX)/include/fluidsynth* .
      displayName: 'Collecting artifacts'
    
    - script: |
        set -ex
        ls libFLAC.so
        ls libfluidsynth-assetloader.so
        ls libfluidsynth.so
        ls libgio-2.0.so
        ls libglib-2.0.so
        ls libgmodule-2.0.so
        ls libgobject-2.0.so
        ls libgthread-2.0.so
        ls libinstpatch-1.0.so
        ls liboboe.so
        ls libogg.so
        ls libopus.so
        ls libsndfile.so
        ls libvorbis.so
        ls libvorbisenc.so
        ls libvorbisfile.so
        ls libpcre.so
        ls libpcreposix.so
      displayName: 'Verify all libs exist'
      workingDirectory: '$(PREFIX)/lib'

    - task: PublishBuildArtifacts@1
      displayName: 'Publishing Artefacts for Android API$(ANDROID_API) $(ANDROID_ABI_CMAKE)'
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: '$(ARTIFACT_NAME)'
        publishLocation: 'Container'

    - script: |
        set -ex
        # as very last step before creating the pipeline cache, remove fluidsynth
        pushd build
        make uninstall
        popd
      displayName: 'Uninstall fluidsynth'
