// The cross-compilation job tests mlpack on a set of low-resource devices.
// First, the cross-compilation host compiles mlpack tests for each
// architecture, and then copies them to the destination host and runs them.

// Define the ARCH_NAME and device name for each host we will run on.
arch_names = ['couscous': 'CORTEXA76',
              'tofu':     'CORTEXA53',
              'chorizo':  'CORTEXA78',
              'tatertot': 'CORTEXA72']

device_names = ['couscous': 'rpi5',
                'tofu': 'rpi3',
                'chorizo': 'jetson-orin-nano',
                'tatertot': 'beaglebone-ai64']

page_sizes = ['couscous': '0x4000' /* 16kb for RPi5s */,
              'tofu': '',
              'chorizo': '',
              'tatertot': '0x10000' /* 64kb */]

pipeline
{
  agent
  {
    // Only use a node that has access to the target hosts.
    label 'cross-compile'
  }

  options
  {
    // Only allow one build at a time of this job.
    disableConcurrentBuilds(abortPrevious: true)

    // We will do checkout manually.
    skipDefaultCheckout()
  }

  stages
  {
    stage('Set up workspace')
    {
      steps
      {
        cleanWs(deleteDirs: true,
                disableDeferredWipeout: true,
                notFailBuild: true)
        checkout scm

        script
        {
          u = load '.jenkins/utils.groovy'
          u.startCheck('Cross-compilation checks', 'Setting up workspace...')
        }

        // Create a directory for our resulting reports.
        sh'mkdir -p reports/'
      }
    }

    stage('Cross-compile mlpack to different targets')
    {
      matrix
      {
        axes
        {
          axis
          {
            name 'target'
            values 'couscous', 'tofu', 'chorizo', 'tatertot'
          }
        }

        stages
        {
          // Cross-compile mlpack tests.
          stage('Cross-compilation tests')
          {
            agent
            {
              docker
              {
                image 'mlpack/mlpack-cross-compile-' +
                    arch_names[env.target].toLowerCase() + ':latest'
                alwaysPull true
                reuseNode true
              }
            }

            environment
            {
              ARCH_NAME = "${arch_names[env.target]}"
              DEVICE_NAME = "${device_names[env.target]}"
              PAGE_SIZE = "${page_sizes[env.target]}"
            }

            steps
            {
              script
              {
                u.updateCheckStatus('Building mlpack for ' + env.target + '...')
              }

              sh '''
                # If a custom page size is needed, set it.
                EXTRA_CMAKE_OPTS='';
                if [ ! -z "$PAGE_SIZE" ];
                then
                  EXTRA_CMAKE_OPTS='"-DCMAKE_CXX_FLAGS=\\"-Wl,-z,common-page-size='$PAGE_SIZE' -Wl,-z,max-page-size='$PAGE_SIZE'\\""';
                fi

                rm -rf build-${target}/
                mkdir build-${target}/
                cd build-${target}/
                cmake \
                    -DBUILD_TESTS=ON \
                    -DARCH_NAME=${ARCH_NAME} \
                    -DCMAKE_CROSSCOMPILING=ON \
                    -DCMAKE_TOOLCHAIN_FILE=../CMake/crosscompile-toolchain.cmake \
                    -DTOOLCHAIN_PREFIX=$TOOLCHAIN_PREFIX \
                    -DCMAKE_SYSROOT=$CMAKE_SYSROOT \
                    -DDOWNLOAD_DEPENDENCIES=ON \
                    $EXTRA_CMAKE_OPTS \
                    ../
                make mlpack_test;
              '''

              script
              {
                u.updateCheckStatus('Testing mlpack on ' + env.target + '...')
              }

              withCredentials([sshUserPrivateKey(
                  credentialsId: 'mlpack-jenkins-cross-compile-rsa-key',
                  keyFileVariable: 'KEY_FILE',
                  passphraseVariable: 'PASSPHRASE')])
              {
                sh'''
                  eval $(ssh-agent -s)
                  echo ${PASSPHRASE} | SSH_ASKPASS=/bin/cat setsid -w ssh-add ${KEY_FILE}

                  # Don't check the host keys, because they won't be saved in
                  # this container anyway.
                  mkdir -p ~/.ssh/
                  echo 'Host *' >> ~/.ssh/config;
                  echo '  StrictHostKeyChecking no' >> ~/.ssh/config;

                  ssh jenkins@${target} -t mkdir -p test_${BRANCH_NAME}_${BUILD_ID}/
                  scp build-${target}/bin/mlpack_test jenkins@${target}:test_${BRANCH_NAME}_${BUILD_ID}/
                  scp -r src/mlpack/tests/data/* jenkins@${target}:test_${BRANCH_NAME}_${BUILD_ID}/
                  scp -r doc/img/cat.jpg jenkins@${target}:test_${BRANCH_NAME}_${BUILD_ID}/

                  # Unpack all compressed test data.
                  ssh jenkins@${target} -t "
                      cd test_${BRANCH_NAME}_${BUILD_ID};
                      find ./ -iname '*.bz2' -exec tar xvf \\{\\} \\;"

                  # We use a lockfile to ensure that we don't run more than one
                  # test on the device at once.  `lockfile` is provided by
                  # procmail.  The configuration here kills any tests that took
                  # more than two hours.
                  mkdir -p reports;
                  ssh jenkins@${target} -t "
                      cd test_${BRANCH_NAME}_${BUILD_ID};
                      lockfile -r-1 -l 7200 ~/mlpack_test.lock;
                      killall -9 mlpack_test;
                      mkdir -p reports;
                      ./mlpack_test -r junit -o reports/mlpack_test.junit.xml;
                      rm -f ~/mlpack_test.lock;" \
                      || echo "Test failure or other problem.";

                  # Clean up afterwards.
                  scp jenkins@${target}:test_${BRANCH_NAME}_${BUILD_ID}/reports/mlpack_test.junit.xml reports/mlpack_test.${target}.junit.xml;
                  ssh jenkins@${target} -t rm -rf test_${BRANCH_NAME}_${BUILD_ID}/;
                '''
              }
            }
          }
        }
      }
    }
  }

  post
  {
    always
    {
      junit(allowEmptyResults: true,
            skipPublishingChecks: true,
            testResults: '**/reports/mlpack_test.*.junit.xml')

      // Clean the workspace.
      cleanWs(cleanWhenNotBuilt: true,
              deleteDirs: true,
              disableDeferredWipeout: true,
              notFailBuild: true)
    }

    success
    {
      script { u.finishCheck('Cross-compilation checks passed.', true) }
    }

    // Mark unstable builds as failed.
    unstable
    {
      script { u.finishCheck('Cross-compilation checks failed.', false) }
    }

    failure
    {
      script { u.finishCheck('Cross-compilation checks failed.', false) }
    }
  }
}
