# 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:12.9.1
var_2: &docker-firefox-image circleci/node:12.9.1-browsers

# **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_3: &cache_key v6-ng-mat-{{ checksum ".bazelversion" }}-{{ checksum "tools/postinstall/apply-patches.js" }}-{{ checksum "WORKSPACE" }}-{{ checksum "yarn.lock" }}
# We want to invalidate the cache if the postinstall patches change. In order to apply new
# patches, a clean version of the node modules is needed. Additionally, we invalidate the cache
# if the Bazel version changes. We do this because otherwise the `bazelisk` cache folder will
# contain all previously used versions and ultimately cause the cache restoring to be slower.
var_4: &cache_fallback_key v6-ng-mat-{{ checksum ".bazelversion" }}-{{ checksum "tools/postinstall/apply-patches.js" }}-

# Settings common to each job
var_5: &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_6: &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_7: &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_8: &save_cache
  save_cache:
    key: *cache_key
    paths:
      - "node_modules"
      - "~/.cache/bazelisk"
      - "~/bazel_repository_cache"

# Decryption token that is used to decode the GCP credentials file in ".circleci/gcp_token".
var_9: &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_10: &yarn_install
  run:
    name: "Installing project dependencies"
    command: yarn install --frozen-lockfile --non-interactive

# Installs all dependencies but does not enforce a frozen lockfile. Helpful when
# the "package.json" is updated as part of a CI job. e.g. when setting up snapshots.
var_11: &yarn_install_loose_lockfile
  run:
    name: "Installing project dependencies"
    command: yarn install --non-interactive

# Sets up the Bazel config which is specific for CircleCI builds.
var_12: &setup_bazel_ci_config
  run:
    name: "Setting up Bazel configuration for CI"
    command: |
      echo "import %workspace%/.circleci/bazel.rc" >> ./.bazelrc

# 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"

# 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

# Sets up the Angular snapshot builds.
var_18: &setup_snapshot_builds
  run:
    name: "Setting up Angular snapshot builds"
    command: node ./scripts/circleci/setup-angular-snapshots.js master

# Filter to skip a job on builds for pull requests.
var_19: &skip_on_pull_requests_filter
  branches:
      ignore:
        - /pull\/\d+/

# Filter which ensures that jobs only run for pull requests.
var_20: &only_on_pull_requests_filter
  branches:
      only:
        - /pull\/\d+/

# -----------------------------
# 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_install
      - *setup_bazel_binary

      # Exclude release and docs packages here as those will be built within
      # the "build_release_packages" and "publish_snapshots" jobs.
      - run: bazel build src/... --build_tag_filters=-docs-package,-release-package

  # --------------------------------------------------------------------------------------------
  # 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_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_install
      - *setup_bazel_binary

      - run: bazel test src/... --build_tag_filters=e2e --test_tag_filters=e2e --build_tests_only

  # ------------------------------------------------------------------------------------------
  # 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_install
      - *setup_bazel_binary

      - run: bazel test src/... --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only

  # ----------------------------------------------------------------------------
  # 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
      - *setup_bazel_ci_config
      - *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-components"
      SAUCE_ACCESS_KEY: "63348201a846-eeb9-3ee4-300f-ea990b8a"
      # 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
    - *setup_bazel_ci_config
    - *yarn_install

    - run: ./scripts/circleci/run-saucelabs-tests.sh

  # ----------------------------------
  # Lint job.
  # ----------------------------------
  lint:
    <<: *job_defaults
    steps:
      - *checkout_code
      - *restore_cache
      - *setup_bazel_ci_config
      - *yarn_install
      - *setup_bazel_binary

      - run:
          name: Checking rollup globals
          command: |
            bazel build //:rollup_globals
            yarn check-rollup-globals $(bazel info bazel-bin)/rollup_globals.json

      - run:
          name: Checking entry-points configuration
          command: |
            bazel build //:entry_points_manifest
            yarn check-entry-point-setup $(bazel info bazel-bin)/entry_points_manifest.json

      - run: ./scripts/circleci/lint-bazel-files.sh
      - run: yarn ownerslint
      - run: yarn stylelint
      - run: yarn tslint

      - *save_cache

  # -------------------------------------------------------------------------------------------
  # Job that builds all release packages. The built packages can be then used in the same
  # workflow to publish snapshot builds.
  # -------------------------------------------------------------------------------------------
  build_release_packages:
    <<: *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_install
      - *setup_bazel_binary

      - run: yarn build
      - 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/

  upload_release_packages:
    <<: *job_defaults
    steps:
      - *checkout_code
      - *restore_cache
      - *attach_release_output
      - *yarn_install

      # Creates package archives and passes in an appropriate meaningful archive suffix. The
      # suffix consists of the pull request number and a short SHA describing the current `HEAD`.
      - run: ./scripts/create-package-archives.js --suffix "pr$CIRCLE_PR_NUMBER-$(git rev-parse --short HEAD)"
      # Upload archives to the CircleCI job artifacts.
      - store_artifacts:
          path: dist/release-archives

  # ----------------------------------------
  # 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_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"

      # The components examples package is not a release package, but we publish it
      # as part of this job to the docs-content repository. It's not contained in the
      # attached release output, so we need to build it here.
      - run: bazel build src/components-examples:npm_package --config=release

      - run: ./scripts/circleci/publish-snapshots.sh

  # -----------------------------------------------------------------
  # Job that ensures that the release output is compatible with ngcc.
  # -----------------------------------------------------------------
  ngcc_compatibility:
    <<: *job_defaults
    resource_class: xlarge
    steps:
      - *checkout_code
      - *restore_cache
      - *attach_release_output
      - *yarn_install

      - run: cp -R dist/releases/* node_modules/@angular/
      - run: yarn ngcc

  # -----------------------------------------------------------------
  # Job that ensures that the release output is compatible with the
  # latest snapshot ngcc changes.
  # -----------------------------------------------------------------
  ngcc_compatibility_snapshot:
    <<: *job_defaults
    resource_class: xlarge
    steps:
      - *checkout_code
      - *restore_cache
      - *attach_release_output
      - *setup_snapshot_builds
      - *yarn_install_loose_lockfile

      - run: cp -R dist/releases/* node_modules/@angular/
      - run: yarn ngcc

  # ----------------------------------------------------------------------------
  # Job that runs the local browser tests against the Angular Github snapshots
  # ----------------------------------------------------------------------------
  snapshot_tests_local_browsers:
    docker:
      - image: *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
    - *setup_snapshot_builds
    - *yarn_install_loose_lockfile
    - *setup_bazel_binary

    - run: bazel test src/... --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only

  # ----------------------------------------------------------------------------
  # Job that runs all Bazel tests against View Engine with the current Angular version
  # specified in the project dev dependencies.
  # ----------------------------------------------------------------------------
  view_engine_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_install
      - *setup_bazel_binary

      # Run project tests with NGC and View Engine.
      - run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only

  # ----------------------------------------------------------------------------
  # Job that runs all Bazel tests against View Engine from angular/angular#master.
  # ----------------------------------------------------------------------------
  view_engine_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
      - *setup_snapshot_builds
      - *yarn_install_loose_lockfile
      - *setup_bazel_binary

      # Run project tests with NGC and View Engine.
      - run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only

  # ----------------------------------------------------------------------------
  # Job that runs all Bazel tests against material-components-web@canary
  # ----------------------------------------------------------------------------
  mdc_snapshot_test_cronjob:
    <<: *job_defaults
    resource_class: xlarge
    environment:
      GCP_DECRYPT_TOKEN: *gcp_decrypt_token
    steps:
      - *checkout_code
      - *restore_cache
      - *setup_bazel_binary
      - *setup_bazel_ci_config
      - *setup_bazel_remote_execution
      - *yarn_install

      # Install the latest canary version of the "material-components-web".
      - run: yarn add material-components-web@canary

      # Setup the components repository to use the MDC snapshot builds.
      # Run project tests with the MDC canary builds.
      - run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --build_tests_only

# ----------------------------------------------------------------------------------------
# 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

  default_workflow:
    jobs:
      - bazel_build:
          filters: *ignore_presubmit_branch_filter
      - view_engine_test:
          filters: *ignore_presubmit_branch_filter
      - api_golden_checks:
          filters: *ignore_presubmit_branch_filter
      - tests_local_browsers:
          filters: *ignore_presubmit_branch_filter
      - tests_browserstack:
          filters: *ignore_presubmit_branch_filter
      - tests_saucelabs:
          filters: *ignore_presubmit_branch_filter
      - e2e_tests:
          filters: *ignore_presubmit_branch_filter
      - build_release_packages:
          filters: *ignore_presubmit_branch_filter
      - upload_release_packages:
          # We don't want to run this job on push builds because for those, the
          # `publish_snapshots` runs, and publishes build artifacts.
          filters: *only_on_pull_requests_filter
          requires:
            - build_release_packages
      - lint:
          filters: *ignore_presubmit_branch_filter
      - ngcc_compatibility:
          filters: *ignore_presubmit_branch_filter
          requires:
            - build_release_packages
      - ngcc_compatibility_snapshot:
          filters: *skip_on_pull_requests_filter
          requires:
            - build_release_packages
      - publish_snapshots:
          filters: *publish_branches_filter
          requires:
            - build_release_packages

  # 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
      - view_engine_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
