# Github action to test for C++ interoperability
#
# This file is modeled after DMD's Github action definition (`runnable_cxx.yml`).
# Its goal is to test interaction with the C++ standard library on multiple platforms.
# Look up the DMD file for more information about reasoning, patterns, caveats, etc...
#
# TODO:
# - Implement Windows + MSVC 2017 support (investigate 2015)
# - Implement Windows + clang support
# - Implement Linux + Clang 32 bits support (if possible)
name: stdcpp

on:
  pull_request:
    branches:
      - master
      - stable
  push:
    branches:
      - master
      - stable
      # Use this branch name in your fork to test changes
      - github-actions

jobs:
  main:
    name: Run
    strategy:
      fail-fast: false
      # Matches the matrix in DMD to support the same platforms
      matrix:
        os: [ macOS-10.15, ubuntu-18.04, windows-2019 ]
        target: [
          clang-9.0.0, clang-8.0.0,
          g++-9, g++-8, g++-7, g++-6, g++-5,
          msvc-2019, msvc-2017, msvc-2015
        ]

        exclude:
          # Ubuntu supports clang and g++
          - { os: ubuntu-18.04, target: msvc-2019 }
          - { os: ubuntu-18.04, target: msvc-2017 }
          - { os: ubuntu-18.04, target: msvc-2015 }
          # OSX only supports clang
          - { os: macOS-10.15, target: g++-9 }
          - { os: macOS-10.15, target: g++-8 }
          - { os: macOS-10.15, target: g++-7 }
          - { os: macOS-10.15, target: g++-6 }
          - { os: macOS-10.15, target: g++-5 }
          - { os: macOS-10.15, target: msvc-2019 }
          - { os: macOS-10.15, target: msvc-2017 }
          - { os: macOS-10.15, target: msvc-2015 }
          # We don't test g++ on Windows as DMD only mangles for MSVC
          - { os: windows-2019, target: g++-9 }
          - { os: windows-2019, target: g++-8 }
          - { os: windows-2019, target: g++-7 }
          - { os: windows-2019, target: g++-6 }
          - { os: windows-2019, target: g++-5 }

          # TODO: Implement support for clang and MSVC2017 on Windows
          # Currently those are still being run by the auto-tester
          # We can hardly test below 2017 in the CI because there's
          # no way to install it via command line
          # (TODO: Test with 2015 as the blog post is slightly ambiguous)
          # https://devblogs.microsoft.com/cppblog/introducing-the-visual-studio-build-tools/
          - { os: windows-2019, target: msvc-2017 }
          - { os: windows-2019, target: msvc-2015 }
          - { os: windows-2019, target: clang-9.0.0 }
          - { os: windows-2019, target: clang-8.0.0 }

        include:
          # Clang boilerplate
          - { target: clang-9.0.0, compiler: clang, cxx-version: 9.0.0 }
          - { target: clang-8.0.0, compiler: clang, cxx-version: 8.0.0 }
          # g++ boilerplace
          - { target: g++-9, compiler: g++, cxx-version: 9.3.0 }
          - { target: g++-8, compiler: g++, cxx-version: 8.4.0 }
          - { target: g++-7, compiler: g++, cxx-version: 7.5.0 }
          - { target: g++-6, compiler: g++, cxx-version: 6.5.0 }
          - { target: g++-5, compiler: g++, cxx-version: 5.5.0 }
          # Platform boilerplate
          - { os: ubuntu-18.04, arch: x86_64-linux-gnu-ubuntu-18.04 }
          - { os: macOS-10.15,  arch: x86_64-apple-darwin }
          # Clang 9.0.0 have a different arch for OSX
          - { os: macOS-10.15, target: clang-9.0.0, arch: x86_64-darwin-apple }

    runs-on: ${{ matrix.os }}
    steps:

    ########################################
    #    Setting up the host D compiler    #
    ########################################
    - name: Prepare compiler
      uses: dlang-community/setup-dlang@v1

    ##############################################
    # Find out which branch we need to check out #
    ##############################################
    - name: Determine base branch
      id: base_branch
      shell: bash
      run: |
        # For pull requests, base_ref will not be empty
        if [ ! -z ${{ github.base_ref }} ]; then
            echo "::set-output name=branch::${{ github.base_ref }}"
        # Otherwise, use whatever ref we have:
        # For branches this in the format 'refs/heads/<branch_name>',
        # and for tags it is refs/tags/<tag_name>.
        else
            echo "::set-output name=branch::${{ github.ref }}"
        fi

    #########################################
    # Checking out up DMD, druntime, Phobos #
    #########################################
    - name: Checkout DMD
      uses: actions/checkout@v2
      with:
        path: dmd
        repository: dlang/dmd
        ref: ${{ steps.base_branch.outputs.branch }}
        persist-credentials: false
    - name: Checkout druntime
      uses: actions/checkout@v2
      with:
        path: druntime
        persist-credentials: false
    - name: Checkout Phobos
      uses: actions/checkout@v2
      with:
        path: phobos
        repository: dlang/phobos
        ref: ${{ steps.base_branch.outputs.branch }}
        persist-credentials: false


    ########################################
    #   Setting up the host C++ compiler   #
    ########################################
    - name: '[Posix] Setting up clang ${{ matrix.cxx-version }}'
      if: matrix.compiler == 'clang' && runner.os != 'Windows'
      run: |
        wget --quiet --directory-prefix=${{ github.workspace }} https://releases.llvm.org/${{ matrix.cxx-version }}/clang+llvm-${{ matrix.cxx-version }}-${{ matrix.arch }}.tar.xz
        tar -x -C ${{ github.workspace }} -f ${{ github.workspace }}/clang+llvm-${{ matrix.cxx-version }}-${{ matrix.arch }}.tar.xz
        TMP_CC='${{ github.workspace }}/clang+llvm-${{ matrix.cxx-version }}-${{ matrix.arch }}/bin/clang'
        # On OSX, the system header are installed via `xcode-select` and not distributed with clang
        # Since some part of the testsuite rely on CC being only a binary (not a command),
        # and config files where only introduced from 6.0.0, use a wrapper script.
        if [ "${{ matrix.os }}" == "macOS-10.15" ]; then
          # Note: heredoc shouldn't be indented
          cat <<EOF > ${TMP_CC}-wrapper
        #!/bin/bash
        ${TMP_CC} -isystem /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/ \$@
        EOF
          # Invoking clang with `clang++` will link the C++ standard library
          # Make sure we got two separate wrapper for this
          cat <<EOF > ${TMP_CC}++-wrapper
        #!/bin/bash
        ${TMP_CC}++ -isystem /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/ \$@
        EOF
          chmod +x ${TMP_CC}-wrapper ${TMP_CC}++-wrapper
          echo "CC=${TMP_CC}-wrapper" >> $GITHUB_ENV
          echo "CXX=${TMP_CC}++-wrapper" >> $GITHUB_ENV
        else
          # Note: druntime's tests use `CXX` and DMD uses `CC`
          echo "CC=${TMP_CC}" >> $GITHUB_ENV
          echo "CXX=${TMP_CC}++" >> $GITHUB_ENV
        fi

    # On OSX and Linux, clang is installed by default and in the path,
    # so make sure ${CC} works
    - name: '[Posix] Verifying installed clang version'
      if: matrix.compiler == 'clang' && runner.os != 'Windows'
      run: |
        set -e
        if ${CXX} --version | grep -q 'version ${{ matrix.cxx-version }}'; then
          ${CXX} --version
          ${CXX} -print-search-dirs -print-libgcc-file-name
        else
            echo "Expected version ${{ matrix.cxx-version }}, from '${CC}', got:"
            ${CXX} --version
            exit 1
        fi

    # G++ is only supported on Linux
    - name: '[Linux] Setting up g++ ${{ matrix.cxx-version }}'
      if: matrix.compiler == 'g++'
      run: |
        # Make sure we have the essentials
        sudo apt-get update
        sudo apt-get install build-essential software-properties-common -y
        # This ppa provides multiple versions of g++
        sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
        sudo apt-get update
        sudo apt-get install ${{ matrix.target }} ${{ matrix.target }}-multilib
        echo "CC=${{ matrix.target }}" >> $GITHUB_ENV
        echo "CXX=${{ matrix.target }}" >> $GITHUB_ENV

    # Make sure ${CC} works and we don't use the $PATH one
    - name: '[Linux] Verifying installed g++ version'
      if: matrix.compiler == 'g++'
      run: |
        set -e
        if ${CXX} --version | grep -q '${{ matrix.target }} (Ubuntu '; then
          ${CXX} --version
        else
            echo "Expected version ${{ matrix.target }}, from '${CXX}', got:"
            ${CXX} --version
            exit 1
        fi

    # Restore or install dmc (and DM make)
    - name: '[Windows] Restore dmc from cache'
      id: cache-dmc
      if: runner.os == 'Windows'
      uses: actions/cache@v1
      with:
        path: ${{ github.workspace }}\tools\
        key: ${{ matrix.os }}-dmc857

    - name: '[Windows] Install dmc'
      if: runner.os == 'Windows' && steps.cache-dmc.outputs.cache-hit != 'true'
      shell: powershell
      run: |
        $url = "http://ftp.digitalmars.com/Digital_Mars_C++/Patch/dm857c.zip"
        $sha256hash = "F51CDFEB45EAF4FFBF7ABF0FE9B3D548B202B4528401005C2C3192B00BC32367"
        Write-Host ('Downloading {0} ...' -f $url)
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        $ProgressPreference = 'SilentlyContinue'
        New-Item -ItemType directory -Path ${{ github.workspace }}\tools\
        Invoke-WebRequest -Uri $url -OutFile '${{ github.workspace }}\tools\dmc.zip'
        if ((Get-FileHash '${{ github.workspace }}\tools\dmc.zip' -Algorithm "SHA256").Hash -ne $sha256hash) {
          exit 1
        }
        Expand-Archive '${{ github.workspace }}\tools\dmc.zip' -DestinationPath ${{ github.workspace }}\tools\

    - name: '[Windows] Add VC toolset to PATH'
      if: runner.os == 'Windows'
      uses: ilammy/msvc-dev-cmd@v1

    - name: '[Windows] Set environment variables'
      if: runner.os == 'Windows'
      shell: bash
      run: |
        echo "VISUAL_STUDIO_LIB_NOT_DM=$(which lib.exe)" >> $GITHUB_ENV
        echo "HOST_DMD=${{ env.DC }}" >> $GITHUB_ENV
        echo "${{ github.workspace }}/tools/dm/bin/" >> $GITHUB_PATH

    ########################################
    #    Building DMD, druntime, Phobos    #
    ########################################
    - name: '[Posix] Build compiler & standard library'
      if: runner.os != 'Windows'
      run: |
        # All hosts are 64 bits but let's be explicit
        ./dmd/src/build.d -j2 MODEL=64
        make -C druntime -f posix.mak -j2 MODEL=64
        make -C phobos   -f posix.mak -j2 MODEL=64
        # Both version can live side by side (they end up in a different directory)
        # However, since clang does not provide a multilib package, only test 32 bits with g++
        #if [ ${{ matrix.compiler }} == "g++" ]; then
        #  ./dmd/src/build.d -j2 MODEL=32
        #  make -C druntime -f posix.mak -j2 MODEL=32
        #  make -C phobos   -f posix.mak -j2 MODEL=32
        #fi
        echo "Skipping 32 bits C++ tests"

    - name: '[Windows] Build compiler & standard library'
      if: runner.os == 'Windows'
      shell: bash
      run: |
        dmd -run dmd/src/build.d -j2 MODEL=64
        if [ $? -ne 0 ]; then return 1; fi
        # Note: Only CC for druntime and AR for Phobos are required ATM,
        # but providing all three to avoid surprise for future contributors
        # Those should really be in the path, though.
        cd druntime
        make -f win64.mak CC=cl.exe LD=link "AR=$VISUAL_STUDIO_LIB_NOT_DM"
        if [ $? -ne 0 ]; then return 1; fi
        cd ../phobos/
        make -f win64.mak CC=cl.exe LD=link "AR=$VISUAL_STUDIO_LIB_NOT_DM"
        if [ $? -ne 0 ]; then return 1; fi
        cd ../

    ########################################
    #        Running the test suite        #
    ########################################
    - name: '[Posix] Run C++ test suite'
      if: runner.os != 'Windows'
      run: |
        make -C druntime -f posix.mak test/stdcpp/.run MODEL=64
        #if [ ${{ matrix.compiler }} == "g++" ]; then
        #  make -C druntime -f posix.mak test/stdcpp/.run MODEL=32
        #fi
        echo "Skipping 32 bits C++ tests"

    - name: '[Windows] Run C++ test suite'
      if: runner.os == 'Windows'
      shell: bash
      run: |
        cd druntime
        make -f win64.mak test_stdcpp CC=cl.exe LD=link "AR=$VISUAL_STUDIO_LIB_NOT_DM"
        if [ $? -ne 0 ]; then return 1; fi

    ########################################
    #     Dump symbols on link failure     #
    ########################################
    - name: '[Posix,Fail] Dump C++ / D binary symbols'
      if: failure() && runner.os != 'Windows'
      run: |
        ls -l druntime/generated/*/release/*/
        for file in druntime/generated/*/release/*/*.o*; do
          echo "========== Symbols for '$file' =========="
          nm $file
        done
