diff --git a/.dockerignore b/.dockerignore index 0967502d..5cafddd8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,3 @@ -_build -deps cover .elixir_ls *.dump diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3da4c6ab..3eb2925c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,6 +18,10 @@ variables: test: stage: test image: "elixir:1.10" + cache: + paths: + - _build/ + - deps/ services: - name: postgres:12 script: script/cibuild @@ -27,9 +31,14 @@ build_image_for_commit: image: "docker:20.10" only: - master + cache: + paths: + - _build/ + - deps/ services: - name: docker:20.10-dind script: + - script/ci/restore-timestamps - docker login "https://${CI_REGISTRY}" -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker pull $CI_REGISTRY_IMAGE:latest || true # This enables fast parallel builds @@ -38,6 +47,9 @@ build_image_for_commit: # Push the commit SHA tagged version to registry. We will later choose to tag that as stable # if everything passes. - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA + # Pull out the built _build/prod directory so we can cache it! + - docker cp `docker create $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA`:/root/app/_build/prod _build + - script/ci/restore-timestamps # If tests pass, tag the commit and update package versions deploy_to_tags: diff --git a/Dockerfile b/Dockerfile index 096d956a..d85377b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,16 +26,18 @@ ADD ./apps/admin/mix.exs /root/app/apps/admin/ ADD ./apps/app/mix.exs /root/app/apps/app/ ADD ./apps/content/mix.exs /root/app/apps/content/ ADD ./apps/core/mix.exs /root/app/apps/core/ +ADD ./_build/ /root/app/_build/ +ADD ./deps/ /root/app/deps/ RUN mix deps.get RUN mix deps.compile -ADD ./apps /root/app/apps - # Leave off here so that we can built assets and compile the elixir app in parallel FROM node:15.0 # Build assets in a node container +ADD ./apps/app/assets/ /root/app/apps/app/assets/ + WORKDIR /root/app/apps/app/assets/ COPY --from=0 /root/app/ /root/app/ RUN npm install @@ -43,6 +45,8 @@ RUN npm run deploy FROM elixir1 +ADD ./apps /root/app/apps + # Resume compilation of the elixir app ADD ./script /root/app/script RUN MAKE=cmake mix compile diff --git a/script/ci/restore-timestamps b/script/ci/restore-timestamps new file mode 100755 index 00000000..84f9619a --- /dev/null +++ b/script/ci/restore-timestamps @@ -0,0 +1,74 @@ +#!/usr/bin/env elixir + +defmodule TimestampRestorer do + @environment System.get_env("MIX_ENV", "dev") + @db_path "_build/#{@environment}/timestamp-database" + + def sha_all do + timestamp_database = load_timestamp_database() + + "**/*.{ex,exs,beam}" + |> Path.wildcard() + |> Enum.reduce(%{}, fn filename, acc -> + {sha, timestamp} = process(filename, timestamp_database) + Map.put(acc, sha, timestamp) + end) + |> write_timestamp_database() + end + + defp load_timestamp_database() do + if File.exists?(@db_path) do + @db_path + |> File.read!() + |> String.split("\n") + |> Enum.reduce(%{}, fn line, acc -> + [sha, timestamp_string] = String.split(line, ":") + {timestamp, ""} = Integer.parse(timestamp_string) + Map.put(acc, sha, timestamp) + end) + else + %{} + end + end + + defp write_timestamp_database(database) do + database + |> Enum.map(fn {key, value} -> "#{key}:#{value}" end) + |> Enum.join("\n") + |> (& File.write!(@db_path, &1)).() + end + + defp process(filename, timestamp_database) do + sha = sha(filename) + {:ok, %{mtime: new_timestamp}} = File.lstat(filename, time: :posix) + + case Map.get(timestamp_database, sha) do + nil -> + :logger.debug("[NEW SHA ] #{filename}: #{new_timestamp}") + timestamp when timestamp < new_timestamp -> + :logger.debug("[RESTORED ] #{filename}: #{timestamp}") + File.touch(filename, timestamp) + timestamp when timestamp >= new_timestamp -> + :logger.debug("[UNCHANGED] #{filename}: #{timestamp}") + end + + {sha, new_timestamp} + end + + defp sha(filename) do + hash_ref = :crypto.hash_init(:sha) + + File.stream!(filename) + |> Enum.reduce(hash_ref, fn chunk, prev_ref-> + new_ref = :crypto.hash_update(prev_ref, chunk) + new_ref + end) + |> :crypto.hash_final() + |> Base.encode16() + |> String.downcase() + end +end + +{time, _result} = :timer.tc(TimestampRestorer, :sha_all, []) + +:logger.info("Restored timestamps in #{time / 1_000_000}s") diff --git a/script/cibuild b/script/cibuild index d2b81bf3..40e8bc26 100755 --- a/script/cibuild +++ b/script/cibuild @@ -6,8 +6,12 @@ set -e mix local.hex --force mix local.rebar --force +script/ci/restore-timestamps + mix deps.get mix ecto.create mix ecto.migrate mix test + +script/ci/restore-timestamps