# Configuration file for https://circleci.com/gh/angular/components

# Note: YAML anchors allow an object to be re-used, reducing duplication.
# The ampersand declares an alias for an object, then later the `<<: *name`
# syntax dereferences it.
# See http://blog.daemonl.com/2016/02/yaml.html
# To validate changes, use an online parser, eg.
# http://yaml-online-parser.appspot.com/

var_1: &docker_image circleci/node:10.16

# **Note**: When updating the beginning of the cache key, also update the cache key to match
# the new cache key prefix. This allows us to take advantage of CircleCI's fallback caching.
# Read more here: https://circleci.com/docs/2.0/caching/#restoring-cache.
var_2: &cache_key v4-ng-mat-{{ checksum "WORKSPACE" }}-{{ checksum "yarn.lock" }}
var_3: &cache_fallback_key v4-ng-mat-

# Settings common to each job
var_4: &job_defaults
  working_directory: ~/ng
  docker:
    - image: *docker_image

# Job step for checking out the source code from GitHub. This also ensures that the source code
# is rebased on top of master.
var_5: &checkout_code
  checkout:
    # After checkout, rebase on top of master. By default, PRs are not rebased on top of master,
    # which we want. See https://discuss.circleci.com/t/1662
    post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"

# Restores the cache that could be available for the current Yarn lock file. The cache usually
# includes the node modules and the Bazel repository cache.
var_6: &restore_cache
  restore_cache:
    keys:
      - *cache_key
      - *cache_fallback_key

# Saves the cache for the current Yarn lock file. We store the node modules and the Bazel
# repository cache in order to make subsequent builds faster.
var_7: &save_cache
  save_cache:
    key: *cache_key
    paths:
      - "node_modules"
      - "~/bazel_repository_cache"

# Decryption token that is used to decode the GCP credentials file in ".circleci/gcp_token".
var_8: &gcp_decrypt_token "angular"

# Job step that ensures that the node module dependencies are installed and up-to-date. We use
# Yarn with the frozen lockfile option in order to make sure that lock file and package.json are
# in sync. Unlike in Travis, we don't need to manually purge the node modules if stale because
# CircleCI automatically discards the cache if the checksum of the lock file has changed.
var_9: &yarn_install
  run:
    name: "Installing project dependencies"
    command: yarn install --frozen-lockfile --non-interactive

# Anchor that can be used to download and install Yarn globally in the bash environment.
var_10: &yarn_download
  run:
    name: "Downloading and installing Yarn"
    command: |
      touch $BASH_ENV
      curl -o- -L https://yarnpkg.com/install.sh | PROFILE=$BASH_ENV bash -s -- --version "1.17.3"
# Sets up the Bazel config which is specific for CircleCI builds.
var_11: &setup_bazel_ci_config
  run:
    name: "Setting up Bazel configuration for CI"
    command: |
      echo "import %workspace%/.circleci/bazel.rc" >> ./.bazelrc
# Sets up a different Docker image that includes a moe recent Firefox version which
# is needed for headless testing.
var_12: &docker-firefox-image
  # TODO(devversion): Temporarily use a image that includes Firefox 62 because the
  # ngcontainer image does include an old Firefox version that does not support headless.
  - image: circleci/node:11.4.0-browsers

# Attaches the release output which has been stored in the workspace to the current job.
# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
var_13: &attach_release_output
  attach_workspace:
    at: dist/

# Branch filter that we can specify for jobs that should only run on publish branches. This filter
# is used to ensure that not all upstream branches will be published as Github builds
# (e.g. revert branches, feature branches)
var_14: &publish_branches_filter
  branches:
    only:
      - master
      # 6.0.x, 7.1.x, etc.
      - /\d+\.\d+\.x/
      # 6.x, 7.x, 8.x etc
      - /\d+\.x/

# Branch filter that is usually applied to all jobs. Since there is no way within CircleCI to
# exclude a branch for all defined jobs, we need to manually specify the filters for each job.
# In order to reduce duplication we use a YAML anchor that just always excludes the "_presubmit"
# branch. We don't want to run Circle for the temporary "_presubmit" branch which is reserved
# for the caretaker.
var_15: &ignore_presubmit_branch_filter
  branches:
    ignore:
      - "_presubmit"
      - "ivy-2019"

# Runs a script that sets up the Bazel remote execution. This will be used by jobs that run
# Bazel primarily and should benefit from remote caching and execution.
var_16: &setup_bazel_remote_execution
  run:
    name: "Setup bazel RBE remote execution"
    command: ./scripts/circleci/bazel/setup-remote-execution.sh

# Sets up the bazel binary globally. We don't want to access bazel through Yarn and NodeJS
# because it could mean that the Bazel child process only has access to limited memory.
var_17: &setup_bazel_binary
  run:
    name: "Setting up global Bazel binary"
    command: ./scripts/circleci/setup_bazel_binary.sh

# **Note**: When updating the beginning of the cache key, also update the fallback cache
# key to match the new cache key prefix. This allows us to take advantage of CircleCI's
# fallback caching. Read more here: https://circleci.com/docs/2.0/caching/#restoring-cache.
var_18: &mdc_deps_cache_key v1-mdc-deps-{{ checksum "/tmp/material-components-web/package-lock.json" }}
var_19: &mdc_deps_fallback_cache_key v1-mdc-deps-

# -----------------------------
# Container version of CircleCI
# -----------------------------
version: 2

# -----------------------------------------------------------------------------------------
# Job definitions. Jobs which are defined just here, will not run automatically. Each job
# must be part of a workflow definition in order to run for PRs and push builds.
# -----------------------------------------------------------------------------------------
jobs:

  # -----------------------------------
  # Job to test that everything builds with Bazel
  # -----------------------------------
  bazel_build:
    <<: *job_defaults
    resource_class: xlarge
    environment:
      GCP_DECRYPT_TOKEN: *gcp_decrypt_token
    steps:
      - *checkout_code
      - *restore_cache
      - *setup_bazel_ci_config
      - *setup_bazel_remote_execution
      - *yarn_download
      - *yarn_install
      - *setup_bazel_binary

      - run: bazel build src/...

  # --------------------------------------------------------------------------------------------
  # Job that runs ts-api-guardian against our API goldens in "tools/public_api_guard".
  # This job fails whenever an API has been updated but not explicitly approved through goldens.
  # --------------------------------------------------------------------------------------------
  api_golden_checks:
    <<: *job_defaults
    resource_class: xlarge
    environment:
      GCP_DECRYPT_TOKEN: *gcp_decrypt_token
    steps:
    - *checkout_code
    - *restore_cache
    - *setup_bazel_ci_config
    - *setup_bazel_remote_execution
    - *yarn_download
    - *yarn_install
    - *setup_bazel_binary

    - run: bazel test tools/public_api_guard/...

  # -----------------------------------------------------------------
  # Job that runs the e2e tests with Protractor and Chromium headless
  # -----------------------------------------------------------------
  e2e_tests:
    <<: *job_defaults
    resource_class: xlarge
    environment:
      GCP_DECRYPT_TOKEN: *gcp_decrypt_token
    steps:
      - *checkout_code
      - *restore_cache
      - *setup_bazel_ci_config
      - *setup_bazel_remote_execution
      - *yarn_download
      - *yarn_install
      - *setup_bazel_binary

      - run: bazel test src/... --build_tag_filters=e2e --test_tag_filters=e2e

  # ------------------------------------------------------------------------------------------
  # Job that runs the unit tests on locally installed browsers (Chrome and Firefox headless).
  # The available browsers are chromium and firefox
  # ------------------------------------------------------------------------------------------
  tests_local_browsers:
    <<: *job_defaults
    resource_class: xlarge
    environment:
      GCP_DECRYPT_TOKEN: *gcp_decrypt_token
    steps:
      - *checkout_code
      - *restore_cache
      - *setup_bazel_ci_config
      - *setup_bazel_remote_execution
      - *yarn_download
      - *yarn_install
      - *setup_bazel_binary

      - run: bazel test src/... --build_tag_filters=-e2e --test_tag_filters=-e2e

  # ----------------------------------------------------------------------------
  # Job that runs the unit tests on Browserstack. The browsers that will be used
  # to run the unit tests on Browserstack are set in: test/browser-providers.js
  # ----------------------------------------------------------------------------
  tests_browserstack:
    <<: *job_defaults
    resource_class: xlarge
    environment:
      BROWSER_STACK_USERNAME: "angularteam1"
      BROWSER_STACK_ACCESS_KEY: "CaXMeMHD9pr5PHg8N7Jq"
    steps:
      - *checkout_code
      - *restore_cache
      - *yarn_download
      - *yarn_install

      - run: ./scripts/circleci/run-browserstack-tests.sh

  # ----------------------------------------------------------------------------
  # Job that runs the unit tests on Saucelabs. The browsers that will be used
  # to run the unit tests on Saucelabs are set in: test/browser-providers.js
  # ----------------------------------------------------------------------------
  tests_saucelabs:
    <<: *job_defaults
    resource_class: xlarge
    environment:
      SAUCE_USERNAME: "angular-ci"
      SAUCE_ACCESS_KEY: "9b988f434ff8-fbca-8aa4-4ae3-35442987"
      # Note: This number should not be too high because otherwise we might run into
      # a rate limit exception.
      KARMA_PARALLEL_BROWSERS: 2
    steps:
    - *checkout_code
    - *restore_cache
    - *yarn_download
    - *yarn_install

    - run: ./scripts/circleci/run-saucelabs-tests.sh

  # ----------------------------------
  # Lint job. Runs the gulp lint task.
  # ----------------------------------
  lint:
    <<: *job_defaults
    steps:
      - *checkout_code
      - *restore_cache
      - *yarn_download
      - *yarn_install

      - run: ./scripts/circleci/lint-bazel-files.sh
      - run: yarn gulp ci:lint

      - *save_cache

  # -------------------------------------------------------------------------------------------
  # Job that builds all release packages with Gulp. The built packages can be then used in the
  # same workflow to publish snapshot builds or test the dev-app with the release packages.
  # -------------------------------------------------------------------------------------------
  build_release_packages:
    <<: *job_defaults
    resource_class: xlarge
    steps:
      - *checkout_code
      - *restore_cache
      - *yarn_download
      - *yarn_install

      - run: yarn gulp ci:build-release-packages
      - run: yarn check-release-output

      # TODO(devversion): replace this with bazel tests that run Madge. This is
      # cumbersome and doesn't guarantee no circular deps for other entry-points.
      - run: yarn madge --circular dist/releases/cdk/schematics/index.js

      # Store the release output in the workspace storage. This means that other jobs
      # in the same workflow can attach the release output to their job.
      - persist_to_workspace:
          root: dist
          paths:
            - "releases/**/*"

      # Since there is no UMD bundle that includes everything from the CDK, we need to move
      # all bundles into a directory. This allows us to store all CDK UMD bundles as job
      # artifacts that can be picked up by the Angular Github bot.
      - run:
          name: Prepare CDK artifacts for publish.
          command: |
            mkdir -p /tmp/cdk-umd-minified-bundles
            cp dist/releases/cdk/bundles/*.umd.min.js /tmp/cdk-umd-minified-bundles
      # Publish bundle artifacts which will be used to calculate the size change.
      # Note: Make sure that the size plugin from the Angular robot fetches the artifacts
      # from this CircleCI job (see .github/angular-robot.yml). Additionally any artifacts need to
      # be stored with the following path format: "{projectName}/{context}/{fileName}"
      # This format is necessary because otherwise the bot is not able to pick up the
      # artifacts from CircleCI. See:
      # https://github.com/angular/github-robot/blob/master/functions/src/plugins/size.ts#L392-L394
      - store_artifacts:
          path: dist/releases/material/bundles/material.umd.min.js
          destination: /angular_material/material_release_output/material.umd.min.js
      - store_artifacts:
          path: /tmp/cdk-umd-minified-bundles
          destination: /angular_material/cdk_release_output/

  # ----------------------------------------
  # Job that publishes the build snapshots
  # ----------------------------------------
  publish_snapshots:
    <<: *job_defaults
    resource_class: xlarge
    environment:
      GCP_DECRYPT_TOKEN: *gcp_decrypt_token
    steps:
      - *checkout_code
      - *restore_cache
      - *attach_release_output
      - *setup_bazel_ci_config
      - *setup_bazel_remote_execution
      - *yarn_download
      - *yarn_install
      - *setup_bazel_binary

      # CircleCI has a config setting to enforce SSH for all github connections.
      # This is not compatible with our mechanism of using a Personal Access Token
      # to publish the build snapshots. In order to fix this, we unset the global option.
      - run: git config --global --unset "url.ssh://git@github.com.insteadof"

      # TODO(devversion): Ideally the "build_release_packages" job should build all packages with
      # Bazel, but for now we mix up the Gulp and bazel setup, so we need to build the package here.
      - run: bazel build src/material-examples:npm_package --config=release

      - run: ./scripts/circleci/publish-snapshots.sh


  # ----------------------------------------------------------------------------
  # Job that runs the local browser tests against the Angular Github snapshots
  # ----------------------------------------------------------------------------
  snapshot_tests_local_browsers:
    docker: *docker-firefox-image
    resource_class: xlarge
    environment:
      GCP_DECRYPT_TOKEN: *gcp_decrypt_token
    steps:
    - *checkout_code
    - *restore_cache
    - *setup_bazel_ci_config
    - *setup_bazel_remote_execution
    - *yarn_download
    - *yarn_install
    - *setup_bazel_binary

    - run: node ./scripts/circleci/setup-angular-snapshots.js --tag master
    - run: bazel test src/... --build_tag_filters=-e2e --test_tag_filters=-e2e

  # ----------------------------------------------------------------------------
  # Job that runs all Bazel tests against Ivy with the current Angular version
  # specified in the project dev dependencies.
  # ----------------------------------------------------------------------------
  ivy_test:
    <<: *job_defaults
    resource_class: xlarge
    environment:
      GCP_DECRYPT_TOKEN: *gcp_decrypt_token
    steps:
      - *checkout_code
      - *restore_cache
      - *setup_bazel_ci_config
      - *setup_bazel_remote_execution
      - *yarn_download
      - *yarn_install
      - *setup_bazel_binary

      # Setup Angular ivy snapshots built with ngtsc but locked to a specific tag. We
      # cannot determine the tag automatically based on the Angular version specified in
      # the "package.json" because the SHA is not known for the given release. Nor can
      # we use ngcc to apply the ivy switches because ngcc currently does not handle the
      # UMD format which is used by Bazel when running tests. UMD processing is in
      # progress and tracked with FW-85.
      - run: node ./scripts/circleci/setup-angular-snapshots.js --tag 9.0.0-next.0-ivy-aot+3122f3415
      # Disable type checking when building with Ivy. This is necessary because
      # type checking is not complete yet and can incorrectly break compilation.
      # Issue is tracked with FW-1004.
      - run: sed -i "s/\(_ENABLE_NG_TYPE_CHECKING = \)True/\1False/g" tools/defaults.bzl
      # Run project tests with ngtsc and the Ivy Angular packages.
      - run: bazel build src/... --build_tag_filters=-docs-package,-e2e --define=compile=aot
      - run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --define=compile=aot

  # ----------------------------------------------------------------------------
  # Job that runs all Bazel tests against Ivy from angular/angular#master.
  # ----------------------------------------------------------------------------
  ivy_snapshot_test_cronjob:
    <<: *job_defaults
    resource_class: xlarge
    environment:
      GCP_DECRYPT_TOKEN: *gcp_decrypt_token
    steps:
      - *checkout_code
      - *restore_cache
      - *setup_bazel_ci_config
      - *setup_bazel_remote_execution
      - *yarn_download
      - *yarn_install
      - *setup_bazel_binary

      # Setup Angular ivy snapshots built with ngtsc.
      - run: node ./scripts/circleci/setup-angular-snapshots.js --tag master-ivy-aot
      # Disable type checking when building with Ivy. This is necessary because
      # type checking is not complete yet and can incorrectly break compilation.
      # Issue is tracked with FW-1004.
      - run: sed -i "s/\(_ENABLE_NG_TYPE_CHECKING = \)True/\1False/g" tools/defaults.bzl
      # Run project tests with ngtsc and the Ivy Angular packages.
      - run: bazel build src/... --build_tag_filters=-docs-package,-e2e --define=compile=aot
      - run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --define=compile=aot

  # ----------------------------------------------------------------------------
  # Job that runs all Bazel tests against material-components-web#master.
  # ----------------------------------------------------------------------------
  mdc_snapshot_test_cronjob:
    <<: *job_defaults
    resource_class: xlarge
    environment:
      GCP_DECRYPT_TOKEN: *gcp_decrypt_token
      MDC_REPO_URL: "https://github.com/material-components/material-components-web.git"
      MDC_REPO_BRANCH: "develop"
      MDC_REPO_TMP_DIR: "/tmp/material-components-web"
    steps:
      - *checkout_code
      - *restore_cache
      - *setup_bazel_binary
      - *setup_bazel_ci_config
      - *setup_bazel_remote_execution
      - *yarn_download
      - *yarn_install

      - run: git clone ${MDC_REPO_URL} --branch ${MDC_REPO_BRANCH} --depth 1 ${MDC_REPO_TMP_DIR}
      - restore_cache:
          keys:
            - *mdc_deps_cache_key
            - *mdc_deps_fallback_cache_key
      - run:
          name: "Installing dependencies for MDC repository"
          # MDC repository does not use Yarn for node dependencies, so in order to respect the
          # lock-file we need to use "npm" when installing dependencies.
          command: cd ${MDC_REPO_TMP_DIR} && npm install
      - save_cache:
          key: *mdc_deps_cache_key
          paths:
            # Repository path must be kept in sync with the `$MDC_REPO_TMP_DIR` env variable.
            # It needs to be hardcoded here, because env variables interpolation is not supported.
            - "/tmp/material-components-web/node_modules"
      - run:
          name: "Building MDC snapshot builds"
          command: |
            cd ${MDC_REPO_TMP_DIR}
            yarn dist && node scripts/cp-pkgs.js
      # Setup the components repository to use the MDC snapshot builds.
      - run: node ./scripts/circleci/setup-mdc-snapshots.js ${MDC_REPO_TMP_DIR}/packages/ $(git -C ${MDC_REPO_TMP_DIR} rev-parse HEAD)
      # Run project tests with the MDC snapshot builds.
      - run: bazel build src/... --build_tag_filters=-docs-package,-e2e
      - run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e

# ----------------------------------------------------------------------------------------
# Workflow definitions. A workflow usually groups multiple jobs together. This is useful if
# one job depends on another.
#
# NOTE: When updating this configuration section, make sure to update GitHub robot
#       config to match the new workflow jobs.
# ----------------------------------------------------------------------------------------
workflows:
  version: 2

  bazel_targets:
    jobs:
      - bazel_build:
          filters: *ignore_presubmit_branch_filter
      - ivy_test:
          filters: *ignore_presubmit_branch_filter
      - api_golden_checks:
          filters: *ignore_presubmit_branch_filter
      - tests_local_browsers:
          filters: *ignore_presubmit_branch_filter

  unit_tests:
    jobs:
      - tests_browserstack:
          filters: *ignore_presubmit_branch_filter
      - tests_saucelabs:
          filters: *ignore_presubmit_branch_filter

  integration_tests:
    jobs:
      - e2e_tests:
          filters: *ignore_presubmit_branch_filter

  release_output:
    jobs:
      - build_release_packages:
          filters: *ignore_presubmit_branch_filter
      - publish_snapshots:
          filters: *publish_branches_filter
          requires:
            - build_release_packages

  # Lint workflow. As we want to lint in one job, this is a workflow with just one job.
  lint:
    jobs:
      - lint:
          filters: *ignore_presubmit_branch_filter

  # Snapshot tests workflow that is scheduled to run all specified jobs every hour.
  # This workflow runs various jobs against the Angular snapshot builds from Github.
  snapshot_tests:
    jobs:
      # Note that we need additional jobs for the cronjob snapshot tests because there
      # is no easy way to detect whether a job runs inside of a cronjob or specific
      # workflow. See: https://circleci.com/ideas/?idea=CCI-I-295
      - snapshot_tests_local_browsers
      - ivy_snapshot_test_cronjob
      - mdc_snapshot_test_cronjob
    triggers:
      - schedule:
          cron: "0 * * * *"
          filters:
            branches:
              only:
                # We only want to run the "master" branch against the snapshot builds because
                # it's not guaranteed that older versions of Angular Material always work
                # with the latest Angular version.
                - master
