version: 2.1

aliases:
  - &opam_env
    TERM: dumb
    OPAMYES: true

  - &run_on_release_tags
    filters:
      tags:
        only: /^v.*/
      branches:
        ignore: /.*/

orbs:
  win: circleci/windows@2.2.0

  opam_windows:
    commands:
      install:
        steps:
          - run:
              name: Install dependencies
              command: |
                choco install --no-progress cygwin
                if (-not $?) { throw "Failed to install cygwin" }
                choco install --no-progress rsync patch diffutils curl make zip unzip git m4 perl mingw64-x86_64-gcc-core --source=cygwin
                if (-not $?) { throw "Failed to install deps from cygwin" }
          - run:
              name: Install opam
              command: |
                $install_dir = "$Env:TEMP\flow\opam_installer"
                New-Item -ItemType Directory $install_dir
                echo "Downloading opam64.tar.xz"
                (New-Object System.Net.WebClient).DownloadFile("https://github.com/fdopen/opam-repository-mingw/releases/download/0.0.0.2/opam64.tar.xz", "$install_dir\opam64.tar.xz")
                echo "Extracting opam64.tar.xz"
                $out_dir = "$install_dir".Replace("\", "/")
                C:\tools\cygwin\bin\tar.exe -x --force-local -f $install_dir\opam64.tar.xz -C "$out_dir"
                echo "Installing opam"
                C:\tools\cygwin\bin\bash.exe -l "$install_dir\opam64\install.sh"
                echo "Done"
      init:
        steps:
          - run:
              name: Init opam
              shell: C:/tools/cygwin/bin/bash.exe --noprofile --norc -eo pipefail
              command: |
                export PATH=$PATH:/usr/local/bin:/usr/bin
                [ -d ~/.opam ] || opam init default 'https://github.com/fdopen/opam-repository-mingw.git#opam2' --bare --disable-sandboxing
      create_switch:
        steps:
          - run:
              name: Create opam switch
              shell: C:/tools/cygwin/bin/bash.exe --noprofile --norc -eo pipefail
              environment:
                PATH: /usr/local/bin:/usr/bin:/cygdrive/c/Windows/system32:/cygdrive/c/Windows:/cygdrive/c/Windows/System32/Wbem:/cygdrive/c/Windows/System32/WindowsPowerShell/v1.0
              command: |
                [ -d _opam ] || opam switch create . ocaml-variants.4.07.1+mingw64c --deps-only --yes -vvv

executors:
  linux-opam:
    docker:
      - image: ocaml/opam2:debian-8
        environment:
          <<: *opam_env
    working_directory: ~/flow
  linux-node:
    docker:
      - image: circleci/node:8
    working_directory: ~/flow
  mac:
    macos:
      xcode: "9.0"
    environment:
      <<: *opam_env
      OPAM_VERSION: 2.0.5
    working_directory: ~/flow
  curl:
    docker:
      - image: appropriate/curl:latest
    working_directory: /flow
  flow-website:
    docker:
      - image: flowtype/flow-website:0.0.5
    working_directory: ~/flow
  awscli:
    docker:
      - image: xueshanf/awscli:latest
    working_directory: ~/flow

commands:
  restore-opam-cache:
    steps:
      - run:
          name: Create cache breaker
          command: |
            echo "ocaml-4.07.1" > .circleci/opamcachebreaker
            opam --version >> .circleci/opamcachebreaker
            cat flowtype.opam >> .circleci/opamcachebreaker
            cat flow_parser.opam >> .circleci/opamcachebreaker
            cat .circleci/config.yml >> .circleci/opamcachebreaker
      - restore_cache:
          keys:
          - v1-opam-cache-{{ arch }}-{{ checksum ".circleci/opamcachebreaker" }}

  save-opam-cache:
    steps:
      - save_cache:
          key: v1-opam-cache-{{ arch }}-{{ checksum ".circleci/opamcachebreaker" }}
          paths:
            - ~/.opam
            - _opam

  create-opam-switch:
    steps:
      - run:
          name: Install deps from opam
          command: |
            eval $(opam env)
            if [ ! -d _opam ]; then
              opam switch create . 4.07.1 --deps-only | cat
            fi

jobs:
  checkout:
    executor: linux-node
    steps:
      - checkout
      - run:
          # This job does a lot of setup to be able to access github, so
          # fetch the list of git tags that Try Flow uses here, instead of
          # hitting github again later.
          name: List versions
          command: .circleci/list_versions.sh
      - persist_to_workspace:
          root: .
          paths: .

  build_linux:
    executor: linux-opam
    steps:
      - attach_workspace:
          at: ~/flow
      - run:
          # installs `zip` and `m4` but also has to fix some apt issues:
          # https://discuss.circleci.com/t/failed-to-fetch-jessie-updates/29246
          # https://www.reddit.com/r/debian/comments/g9is3p/debian_8_jessie_keyexpired_drive_my_crazy/
          # TODO: remove this when updating to ocaml/opam2:debian-9
          name: Install deps
          command: |
            sudo rm /etc/apt/sources.list
            echo "deb [trusted=yes] http://archive.debian.org/debian/ jessie main contrib non-free" | sudo tee -a /etc/apt/sources.list
            echo "deb [trusted=yes] http://archive.debian.org/debian/ jessie-backports main contrib non-free" | sudo tee -a /etc/apt/sources.list
            echo "deb-src [trusted=yes] http://archive.debian.org/debian/ jessie main contrib non-free" | sudo tee -a /etc/apt/sources.list
            echo "deb-src [trusted=yes] http://archive.debian.org/debian/ jessie-backports main contrib non-free" | sudo tee -a /etc/apt/sources.list
            echo "Acquire::Check-Valid-Until false;" | sudo tee -a /etc/apt/apt.conf.d/10-nocheckvalid
            echo 'Package: *\nPin: origin "archive.debian.org"\nPin-Priority: 500' | sudo tee -a /etc/apt/preferences.d/10-archive-pin
            sudo apt-get update && sudo apt-get --yes install zip m4
      - restore-opam-cache
      - run:
          name: Update opam repo
          command: |
            opam remote list -s | grep upstream >/dev/null || \
              opam remote add upstream https://opam.ocaml.org --all-switches --set-default | cat
            opam update | cat
      - create-opam-switch
      - run:
          name: Install extra deps from opam
          command: opam install js_of_ocaml.3.4.0 | cat
      - save-opam-cache
      - run:
          name: Build flow
          command: |
            opam exec -- make bin/flow dist/flow.zip
            mkdir -p bin/linux && cp bin/flow bin/linux/flow
      - run:
          name: Build libflowparser
          command: opam exec -- make -C src/parser dist/libflowparser.zip
      - run:
          name: Build flow.js
          command: opam exec -- make js
      - run:
          name: Build flow_parser.js
          command: opam exec -- make -C src/parser js
      - run:
          name: Create artifacts
          command: |
            cp dist/flow.zip dist/flow-linux64.zip
            cp src/parser/dist/libflowparser.zip dist/libflowparser-linux64.zip
            cp src/parser/flow_parser.js dist/flow_parser.js
      - persist_to_workspace:
          root: .
          paths:
            - bin/linux/flow
            - bin/flow.js
            - dist/flow-linux64.zip
            - dist/libflowparser-linux64.zip
            - dist/flow_parser.js
      - store_artifacts:
          path: dist/flow-linux64.zip
          destination: flow-linux64.zip
      - store_artifacts:
          path: dist/libflowparser-linux64.zip
          destination: libflowparser-linux64.zip
      - store_artifacts:
          path: bin/flow.js
          destination: flow.js
      - store_artifacts:
          path: dist/flow_parser.js
          destination: flow_parser.js

  build_macos:
    executor: mac
    steps:
      - attach_workspace:
          at: ~/flow
      - run:
          name: Install opam
          command: |
            if ! [ -x "$(command -v opam)" ]; then
              TMP=${TMPDIR:-/tmp}
              curl -sL -o "$TMP/opam" "https://github.com/ocaml/opam/releases/download/$OPAM_VERSION/opam-$OPAM_VERSION-x86_64-macos"
              install -m 755 "$TMP/opam" "/usr/local/bin/opam"
              rm -f "$TMP/opam"
            fi
      - restore-opam-cache
      - run:
          name: Init opam
          command: |
            [ -d ~/.opam ] || opam init --bare --disable-sandboxing
      - create-opam-switch
      - save-opam-cache
      - run:
          name: Build flow
          command: |
            opam exec -- make bin/flow dist/flow.zip
            mkdir -p bin/macos && cp bin/flow bin/macos/flow
      - run:
          name: Build libflowparser
          command: opam exec -- make -C src/parser dist/libflowparser.zip
      - run:
          name: Create artifacts
          command: |
            cp dist/flow.zip dist/flow-osx.zip
            cp src/parser/dist/libflowparser.zip dist/libflowparser-osx.zip
      - persist_to_workspace:
          root: .
          paths:
            - bin/macos/flow
            - dist/flow-osx.zip
            - dist/libflowparser-osx.zip
      - store_artifacts:
          path: dist/flow-osx.zip
          destination: flow-osx.zip
      - store_artifacts:
          path: src/parser/dist/libflowparser.zip
          destination: libflowparser-osx.zip

  build_win:
    executor: win/default
    environment:
      - FLOW_TMP_DIR: 'C:\tmp\flow'
      - OPAMDOWNLOADJOBS: 1
    steps:
      - run:
          name: Set up workspace
          command: |
            New-Item -ItemType Directory $Env:FLOW_TMP_DIR
      - opam_windows/install
      - attach_workspace:
          at: .
      - run:
          name: Create cache breaker
          shell: C:/tools/cygwin/bin/bash.exe --noprofile --norc -eo pipefail
          command: |
            /usr/local/bin/opam --version > /cygdrive/c/tmp/flow/opamcachebreaker
            echo "ocaml-4.07.1" >> /cygdrive/c/tmp/flow/opamcachebreaker
            /usr/bin/cat flowtype.opam >> /cygdrive/c/tmp/flow/opamcachebreaker
            /usr/bin/cat flow_parser.opam >> /cygdrive/c/tmp/flow/opamcachebreaker
            /usr/bin/cat .circleci/config.yml >> /cygdrive/c/tmp/flow/opamcachebreaker
      - restore_cache:
          keys:
          - opam-cache-{{ arch }}-{{ checksum "C:/tmp/flow/opamcachebreaker" }}
      - opam_windows/init
      - opam_windows/create_switch
      - save_cache:
          key: opam-cache-{{ arch }}-{{ checksum "C:/tmp/flow/opamcachebreaker" }}
          paths:
            - C:/tools/cygwin/home/circleci/.opam
            - _opam
      - run:
          name: Build flow.exe
          shell: C:/tools/cygwin/bin/bash.exe --noprofile --norc -eo pipefail
          environment:
            PATH: /usr/local/bin:/usr/bin:/cygdrive/c/Windows/system32:/cygdrive/c/Windows:/cygdrive/c/Windows/System32/Wbem:/cygdrive/c/Windows/System32/WindowsPowerShell/v1.0
          command: |
             eval $(opam env)
             make bin/flow.exe dist/flow.zip
             mkdir -p bin/win64 && cp bin/flow.exe bin/win64/flow.exe
             cp dist/flow.zip dist/flow-win64.zip
      - run:
          name: Build parser test runner
          shell: C:/tools/cygwin/bin/bash.exe --noprofile --norc -eo pipefail
          environment:
            PATH: /usr/local/bin:/usr/bin:/cygdrive/c/Windows/system32:/cygdrive/c/Windows:/cygdrive/c/Windows/System32/Wbem:/cygdrive/c/Windows/System32/WindowsPowerShell/v1.0
          command: |
            eval $(opam env)
            make -C src/parser/ ../../_build/src/parser/test/run_tests.native
            cp _build/src/parser/test/run_tests.native bin/win64/run_parser_tests.exe
      - persist_to_workspace:
          root: .
          paths:
            - bin/win64/flow.exe
            - dist/flow-win64.zip
            - bin/win64/run_parser_tests.exe
      - store_artifacts:
          path: .\dist\flow-win64.zip
          destination: flow-win64.zip

  runtests_linux:
    executor: linux-node
    environment:
      FLOW_RUNTESTS_PARALLELISM: 8
    steps:
      - attach_workspace:
          at: ~/flow
      - run:
          name: Run tests
          command: ./runtests.sh bin/linux/flow | cat

  runtests_macos:
    executor: mac
    steps:
      - attach_workspace:
          at: ~/flow
      - run:
          name: Run tests
          command: ./runtests.sh bin/macos/flow | cat

  tool_test_linux:
    executor: linux-node
    steps:
      - attach_workspace:
          at: ~/flow
      - run:
          name: Install tool deps from yarn
          command: (cd packages/flow-dev-tools && yarn install | cat) # Force no tty mode
      - run:
          name: Run tool tests
          command: ./tool test -p 4 --bin bin/linux/flow | cat # Limit parallelism

  tool_test_macos:
    executor: mac
    steps:
      - attach_workspace:
          at: ~/flow
      - run:
          name: Install tool deps from yarn
          command: (cd packages/flow-dev-tools && yarn install | cat) # Force no tty mode
      - run:
          name: Run tool tests
          command: ./tool test --bin bin/macos/flow | cat

  tool_test_win:
    executor: win/default
    steps:
      - attach_workspace:
          at: .
      - run:
          name: Install Yarn
          command: choco install yarn
      - run:
          name: Install tool deps from yarn
          command: |
            cd packages/flow-dev-tools
            yarn install --ignore-scripts --pure-lockfile
      - run:
          name: Run tool tests
          shell: bash
          command: ./tool test --bin bin/win64/flow.exe --parallelism 1 --max-errored-tests-pct 20

  parser_test_win:
    executor: win/default
    steps:
      - attach_workspace:
          at: .
      - run:
          name: Test parser
          command: |
            .\bin\win64\run_parser_tests.exe .\src\parser\test\flow\
            if ($LASTEXITCODE -gt 0) {
              Throw "flow parser hardcoded ocaml tests exited with error code: $LASTEXITCODE"
            }

  npm_pack:
    executor: linux-node
    steps:
      - attach_workspace:
          at: ~/flow
      - run:
          name: Pack flow-parser
          command: |
            mkdir -p packages/flow-parser/dist/
            cp dist/flow_parser.js packages/flow-parser/dist/flow_parser.js
            make dist/npm-flow-parser.tgz
      - run:
          name: Pack flow-parser-bin
          command: |
            mkdir -p packages/flow-parser-bin/dist/release/
            cp dist/libflowparser-linux64.zip packages/flow-parser-bin/dist/release/libflowparser-linux64.zip
            cp dist/libflowparser-osx.zip packages/flow-parser-bin/dist/release/libflowparser-osx.zip
            make dist/npm-flow-parser-bin.tgz
      - run:
          name: Pack flow-remove-types and flow-node
          command: |
            rm -rf packages/flow-node
            cp -r packages/flow-remove-types/ packages/flow-node/
            sed -i '0,/flow-remove-types/s//flow-node/' packages/flow-node/package.json
            make dist/npm-flow-remove-types.tgz
            make dist/npm-flow-node.tgz
      - persist_to_workspace:
          root: .
          paths:
            - dist/npm-flow-parser.tgz
            - dist/npm-flow-parser-bin.tgz
            - dist/npm-flow-node.tgz
            - dist/npm-flow-remove-types.tgz
      - store_artifacts:
          path: dist/npm-flow-parser.tgz
          destination: npm-flow-parser.tgz
      - store_artifacts:
          path: dist/npm-flow-parser-bin.tgz
          destination: npm-flow-parser-bin.tgz
      - store_artifacts:
          path: dist/npm-flow-node.tgz
          destination: npm-flow-node.tgz
      - store_artifacts:
          path: dist/npm-flow-remove-types.tgz
          destination: npm-flow-remove-types.tgz

  npm_deploy:
    executor: linux-node
    steps:
    - attach_workspace:
        at: ~/flow
    - run:
        name: Deploy to npm
        command: .circleci/deploy_npm.sh

  github_linux:
    executor: curl
    steps:
    - attach_workspace:
        at: /flow
    - run:
        name: Upload Linux binary
        command: .circleci/github_upload.sh dist/flow-linux64.zip "flow-linux64-$CIRCLE_TAG.zip"
    - run:
        name: Upload Linux libflowparser
        command: .circleci/github_upload.sh dist/libflowparser-linux64.zip "libflowparser-linux64-$CIRCLE_TAG.zip"

  github_macos:
    executor: curl
    steps:
    - attach_workspace:
        at: /flow
    - run:
        name: Upload Mac binary
        command: .circleci/github_upload.sh dist/flow-osx.zip "flow-osx-$CIRCLE_TAG.zip"
    - run:
        name: Upload Mac libflowparser
        command: .circleci/github_upload.sh dist/libflowparser-osx.zip "libflowparser-osx-$CIRCLE_TAG.zip"

  github_win:
    executor: curl
    steps:
    - attach_workspace:
        at: /flow
    - run:
        name: Upload Windows binary
        command: .circleci/github_upload.sh dist/flow-win64.zip "flow-win64-$CIRCLE_TAG.zip"

  flow_bin_deploy:
    executor: linux-node
    steps:
    - attach_workspace:
        at: ~/flow
    - run:
        name: Deploy flow-bin
        command: .circleci/deploy_flow_bin.sh

  website_deploy:
    executor: flow-website
    steps:
    - attach_workspace:
        at: ~/flow
    - restore_cache:
        keys:
          - v2-node-{{ arch }}-{{ .Branch }}-{{ checksum "/etc/os-release" }}-{{ checksum "website/yarn.lock" }}
    - run:
        name: Install yarn deps
        command: cd website && yarn install
    - save_cache:
        key: v2-node-{{ arch }}-{{ .Branch }}-{{ checksum "/etc/os-release" }}-{{ checksum "website/yarn.lock" }}
        paths:
          - website/node_modules
    - restore_cache:
        keys:
          - v2-gem-{{ arch }}-{{ .Branch }}-{{ checksum "/etc/os-release" }}-{{ checksum "website/Gemfile.lock" }}
    - run:
        name: Install gems
        command: |
          cd website
          bundle config set path 'vendor/bundle'
          bundle install
          bundle exec s3_website install
    - save_cache:
        key: v2-gem-{{ arch }}-{{ .Branch }}-{{ checksum "/etc/os-release" }}-{{ checksum "website/Gemfile.lock" }}
        paths:
          - website/vendor/bundle
    - run:
        name: Build website
        command: PATH=~/flow/bin/linux:$PATH .circleci/build_website.sh
    - run:
        name: Publish website
        command: |
          cd website
          bundle exec s3_website push --site ~/flow/dist/flow.org

  # deploys flow.js and the libdefs to Try Flow on release branches
  try_flow_deploy:
    executor: awscli
    steps:
    - attach_workspace:
        at: ~/flow
    - run:
        name: Assemble files
        command: |
          rm -rf ~/try_flow
          mkdir ~/try_flow
          cp bin/flow.js ~/try_flow/flow.js
          cp -r lib ~/try_flow/flowlib
    - run:
        name: Upload to AWS
        command: aws s3 cp ~/try_flow s3://${S3_BUCKET}/static/${CIRCLE_TAG}/ --recursive

workflows:
  build_and_test:
    jobs:
      - checkout
      - build_linux:
          requires:
            - checkout
      - build_macos:
          requires:
            - checkout
      - build_win:
          requires:
            - checkout
      - runtests_linux:
          requires:
            - build_linux
      - runtests_macos:
          requires:
            - build_macos
      - tool_test_linux:
          requires:
            - build_linux
      - tool_test_macos:
          requires:
            - build_macos
      - tool_test_win:
          requires:
            - build_win
      - parser_test_win:
          requires:
            - build_win
      - npm_pack:
          requires:
            - build_linux
            - build_macos

      # only deploy the website from master, so that it can include docs/blog
      # posts about an upcoming release, and so the release branch doesn't
      # revert those changes.
      - website_deploy:
          requires:
            - build_linux
          filters:
            branches:
              only: master

  build_and_deploy:
    jobs:
      - checkout:
          <<: *run_on_release_tags
      - build_linux:
          <<: *run_on_release_tags
          requires:
            - checkout
      - build_macos:
          <<: *run_on_release_tags
          requires:
            - checkout
      - build_win:
          <<: *run_on_release_tags
          requires:
            - checkout
      - npm_pack:
          <<: *run_on_release_tags
          requires:
            - build_linux
            - build_macos
      - npm_deploy:
          <<: *run_on_release_tags
          requires:
            - npm_pack
      - github_linux:
          <<: *run_on_release_tags
          requires:
            - build_linux
      - github_macos:
          <<: *run_on_release_tags
          requires:
            - build_macos
      - github_win:
          <<: *run_on_release_tags
          requires:
            - build_win
      - flow_bin_deploy:
          <<: *run_on_release_tags
          requires:
            - github_linux
            - github_macos
            - github_win
      - try_flow_deploy:
          <<: *run_on_release_tags
          requires:
            - build_linux
