Project

General

Profile

Download (13.5 KB) Statistics
| Branch: | Tag: | Revision:
1
# This reusable CI script is licensed under the MIT license.
2
# See the complete license text at the end.
3

    
4
### Configuration section
5

    
6
# The versions of the compiler to test the project against.
7
.build-matrix:
8
  # parallel:
9
    # https://docs.gitlab.com/ee/ci/yaml/README.html#parallel-matrix-jobs
10
    # matrix:
11
    # - OCAML_COMPILER: "4.00.1"
12
    # - OCAML_COMPILER: "4.01.0"
13
    # - OCAML_COMPILER: "4.02.1"
14
    # - OCAML_COMPILER: "4.02.3"
15
    # - OCAML_COMPILER: "4.03.0"
16
    # - OCAML_COMPILER: "4.04.2"
17
    # - OCAML_COMPILER: "4.05.0"
18
    # - OCAML_COMPILER: "4.06.1"
19
    # - OCAML_COMPILER: "4.07.1"
20
    # - OCAML_COMPILER: "4.08.1"
21
    # - OCAML_COMPILER: "4.09.1"
22
    # - OCAML_COMPILER: "4.10.0"
23
    variables:
24
      OCAML_COMPILER: "4.11.1"
25

    
26
variables:
27
  CLEAN_OPAM_CACHE: "false"
28
  CLEAN_DUNE_CACHE: "false"
29
  # If CLEAN_OPAM_CACHE is set to "true", the opam switch from previous CI jobs
30
  # will not be reused.
31
  # If CLEAN_DUNE_CACHE is set to "true", the dune _build directory
32
  # from previous CI jobs will not be reused.
33

    
34
  # In particular, you can run a manual pipeline
35
  # with either variable set to "true" to purge a faulty cache.
36

    
37
  # To run a manual gitlab pipeline, on your project go to
38
  # "CI/CD" > pipelines, click "Run pipeline", the interface
39
  # then offers to override variables, so you can change the
40
  # value of any variable defined here.
41

    
42
  DUNE_BUILD_TARGETS: "@all"
43
  DUNE_TEST_TARGETS: "" #"@runtest"
44
  DUNE_DOC_TARGETS: "" #"@doc"
45
  # If you make one of these variables empty (: ""),
46
  # the corresponding build step will be skipped.
47
  # Setting them to other values can refine the build
48
  # process, for example "@foo/runtest @bar/runtest" would only
49
  # run the tests inside directories 'foo/' and 'bar/'
50
  # (see Dune documentation on target aliases).
51

    
52

    
53
# This CI script is written to be portable across projects.
54
# None of the code below hardcodes project filenames,
55
# OCaml versions or other project-specific details.
56
#
57
# If you want to add new behavior for your project, we recommend
58
# trying to factorize it with project-agnostic code below,
59
# and project-specific or manually-overridable choices in the
60
# 'variables' section above.
61

    
62

    
63
### Hacking advice
64
#
65
# Reference documentation for Gitlab CI configuration files:
66
#  https://docs.gitlab.com/ee/ci/yaml/
67
#
68
# If you edit this file and make syntax errors, the default
69
# error messages will not help you, but Gitlab offers a "CI Lint"
70
# service that provides very nice error messages:
71
#   https://docs.gitlab.com/ee/ci/lint.html
72
#
73
# To use this linter, go to "CI/CD > Pipelines", click the "CI Lint"
74
# button in the top right, copy-paste your .gitlab-ci.yml file in
75
# the form and click "Validate".
76
#
77
# We recommend systematically using the CI Lint whenever you change
78
# this file, it can save you from wasted time and frustration.
79

    
80

    
81
### Stages
82

    
83
stages:
84
  - build        # build the project and run its tests
85
  - deploy       # deploys a website to Pages from the 'pages' branch
86

    
87

    
88
### Build stage
89
#
90
# build the project and run its tests
91

    
92
build:
93
  stage: build
94

    
95
  extends: .build-matrix # defines OCAML_COMPILER
96

    
97
  variables:
98
    ARTIFACTS: "artifacts/$OCAML_COMPILER"
99
    # a local shortcut for the per-compiler artifact repository
100

    
101
    FF_USE_FASTZIP: "true"
102
    # A workaround against a bug in gitlab-runner's default
103
    # unzipping implementation, which partially breaks caching for the dune _build cache.
104
    # See https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27496 for more details.
105

    
106
  artifacts:
107
    paths:
108
      - artifacts/$OCAML_COMPILER
109

    
110
  # run this job only if a 'dune-project' file exists;
111
  # (In particular, this will not run in your "pages" branch
112
  # if it does not itself use Dune.)
113
  rules:
114
    # https://docs.gitlab.com/ee/ci/yaml/#rules
115
    - exists:
116
        - dune-project
117

    
118
  # This CI script uses a local switch, so we don't need
119
  # a docker image with a pre-installed OCaml version, just opam.
120
  # See https://hub.docker.com/r/ocaml/opam/ for other images.
121
  image: ocaml/opam:debian-testing-opam
122

    
123
  # We use a local opam switch in `./_opam` that is cached
124
  # by Gitlab, and reused across all branches and pull requests.
125
  cache:
126
    # https://docs.gitlab.com/ee/ci/yaml/#cache
127
    key: $OCAML_COMPILER
128
    # keep a distinct cache for each compiler version
129
    paths:
130
      - _opam
131
      # Reusing the same opam environment over a long time might result into
132
      # unnatural choice of dependencies: repeatedly installing and updating
133
      # dependencies may result in a different solver choices than doing
134
      # a fresh setup.
135
      #
136
      # You can manually clean the _opam cache by running a manual pipeline
137
      # with the variable CLEAN_OPAM_CACHE set to "true".
138

    
139
      - _build
140
      # You can manually clean the dune _build cache by running a manual pipeline
141
      # with the variable CLEAN_DUNE_CACHE set to "true".
142

    
143
  script:
144
    - echo "Acquire::http::Proxy \"http://proxy.isae.fr:3128\";" | sudo tee /etc/apt/apt.conf > /dev/null
145
    - if [ "$CLEAN_OPAM_CACHE" == "true" ]; then echo "we clean the _opam cache as explicitly requested"; rm -fR _opam; fi
146
    - if [ "$CLEAN_DUNE_CACHE" == "true" ]; then echo "we clean the dune _build cache as explicitly requested"; rm -fR _build; fi
147
    # Note: Gitlab supports multi-line scripts with "- |" or "- >", but the
148
    # display in the logs does not show the whole script being run, reducing
149
    # understandability. We only use multi-line scripts for "echo" line,
150
    # and otherwise keep long one-liners.
151
    #   https://docs.gitlab.com/ee/ci/yaml/script.html#split-long-commands
152

    
153
    # see https://docs.gitlab.com/ee/ci/jobs/index.html#custom-collapsible-sections
154
    # to understand the "section_{start,end}:`date +%s`:<name>\r\e[0K human-readable text" pattern
155
    - echo -e "section_start:`date +%s`:setup_switch\r\e[0K setup opam switch"
156
    - if [ -d _opam ]; then echo "we reuse the local opam switch from the CI cache"; fi
157

    
158
    - if [ ! -d _opam ]; then echo "no local switch in the CI cache, we setup a new switch"; opam switch create --yes --no-install . $OCAML_COMPILER; fi
159
    # --no-install prevents installing the package or its dependencies.
160
    # we want to setup the external dependencies (depext) first, see below.
161
    - echo -e "section_end:`date +%s`:setup_switch\r\e[0K"
162

    
163
    - echo -e "section_start:`date +%s`:setup_deps\r\e[0K setup the package dependencies"
164
    # (Note: when opam 2.1 will be available with native depext integration,
165
    # the complex dance below will be replaceable by just
166
    #   opam install . --deps-only --locked --with-test --with-doc --yes
167
    # which should start by installing depexts
168
    # (possibly we will need to set some explicit environment variable first).
169
    - opam install depext --yes
170
    - opam install . --dry-run --deps-only --locked --with-test --with-doc --yes | awk '/-> installed/{print $3}' | xargs opam depext -iy
171
    # the magical command above comes from https://github.com/ocaml/opam/issues/3790
172
    # it installs both external dependencies and opam dependencies
173
    - echo "(we used --locked to honor any lockfiles if present)"
174
    - echo -e "section_end:`date +%s`:setup_deps\r\e[0K"
175

    
176
    - echo -e "section_start:`date +%s`:project_build\r\e[0K build the project"
177
    - eval $(opam env)
178
    - if [ "$DUNE_BUILD_TARGETS" != "" ]; then dune build $DUNE_BUILD_TARGETS --display short; else echo "skipped (DUNE_BUILD_TARGETS is empty)"; fi
179
    - echo -e "section_end:`date +%s`:project_build\r\e[0K"
180

    
181
    - echo -e "section_start:`date +%s`:project_tests\r\e[0K run the tests"
182
    - if [ "$DUNE_TEST_TARGETS" != "" ]; then dune build $DUNE_TEST_TARGETS --display short; else echo "skipped (DUNE_TEST_TARGETS is empty)"; fi
183
    - echo -e "section_end:`date +%s`:project_tests\r\e[0K"
184

    
185
    - echo -e "section_start:`date +%s`:project_doc\r\e[0K build the documentation"
186
    - if [ "$DUNE_DOC_TARGETS" != "" ]; then dune build $DUNE_DOC_TARGETS --display short; else echo "skipped (DUNE_DOC_TARGETS is empty)"; fi
187
    - echo -e "section_end:`date +%s`:project_doc\r\e[0K"
188

    
189
    - echo -e "section_start:`date +%s`:artifacts\r\e[0K populating the artifacts"
190
    - mkdir -p $ARTIFACTS
191
    - >
192
      echo "Build artifacts will be available at
193
              $CI_JOB_URL/artifacts/browse/$ARTIFACTS
194
            Note: by default Gitlab only keeps them for a few weeks."
195
    - >
196
      if [ "$DUNE_DOC_TARGETS" != "" ]; then
197
        cp -r _build/default/_doc/_html $ARTIFACTS/doc;
198
        echo "Documentation:
199
              $CI_JOB_URL/artifacts/browse/$ARTIFACTS/doc/index.html";
200
      fi
201
    - >
202
      if [ -f _build/log ];
203
      then
204
        mkdir -p $ARTIFACTS/_build;
205
        cp _build/log $ARTIFACTS/_build/log
206
        echo "Dune build log:
207
              $CI_JOB_URL/artifacts/browse/$ARTIFACTS/_build/log";
208
      fi
209
    - echo -e "section_end:`date +%s`:artifacts\r\e[0K"
210

    
211

    
212
### CI performance
213

    
214
# Performance analysis of one 'build' job on a small, simple library project
215
# (the project build/test/doc time is basically neglectible) on gitlab.com:
216
#
217
# (To get those numbers for your project, just look at a passing build and fold all
218
#  log sections.)
219
#
220
# 2m total
221
#   56s preparing the docker image and CI environment
222
#   14s restoring the _opam cache
223
#   10s installing the project dependencies
224
#       (external dependencies are not cached, opam dependencies are cached)
225
#    3s project build, tests, doc
226
#   31s saving the _opam cache
227
#    6s uploading artifacts
228
#
229
# When running with CLEAN_OPAM_CACHE=true, the costs are similar, except for
230
#  7m30s building the local opam switch
231
#  2m07s building the project dependencies (external and opam)
232
# This shows that caching the local _opam switch is very important, as
233
# its setup time dominates the build (for a small project).
234
#
235
# Another thing that is clear from these numbers is that splitting
236
# your CI in several jobs could be fairly expensive: for each job you
237
# pay 56s seconds of docker setup, plus 14s to restore the _opam
238
# cache. (The 31s to save the cache afterwards can be avoided by using
239
# a "policy: pull" setting if the job uses the _opam in a read-only
240
# way.)
241
#
242
# This is why this CI script does everything in a single 'build' step.
243

    
244

    
245
### Deploy stage
246
#
247
# pushing some project artifacts to the web.
248

    
249
# The "pages" rule uploads a custom website whenever someones
250
# pushes a commit to the "pages" branch of the repository.
251
#
252
# The content of the website is the sub-filesystem
253
# located in the "docs/" subdirectory of the branch index.
254
#
255
# This behavior emulates the way per-repository Github pages work.
256
#
257
# If the repository is at
258
#   https://gitlab.com/<group>/<project>,
259
# then the published website will be at
260
#   https://<group>.gitlab.io/<project>
261
# In general see
262
#   https://docs.gitlab.com/ee/user/project/pages/getting_started_part_one.html#gitlab-pages-default-domain-names
263
#
264
# We previously used a setup where the documentation was pushed to
265
# Gitlab Pages automatically when pushing a tag (a new release). This
266
# was much less flexible, with no easy way for users to publish web
267
# content *other* than the documentation, or to control when the
268
# content should be refreshed.
269
#
270
# It is fairly simple to publish the documentation
271
# from any branch with the current interface.
272
# Here is an example script:
273
#
274
#    # compute the current commit hash and branch name
275
#    git rev-parse --short HEAD > /tmp/commit
276
#    git rev-parse --abbrev-ref HEAD > /tmp/branch
277
#    cat .gitlab-ci.yml /tmp
278
#
279
#    # move to the 'pages' branch and commit the documentation there
280
#    git checkout pages
281
#    mkdir -p docs
282
#    git rm --ignore-unmatch -r docs > /dev/null
283
#    cp -r ${DOC_PATH} docs
284
#    git add docs
285
#    # we ensure that the CI configuration is present on the 'pages' branch,
286
#    # otherwise pushing from there may not deploy as expected.
287
#    cp /tmp/.gitlab-ci.yml .
288
#    git add .gitlab-ci.yml
289
#    git commit -m "documentation for $(cat /tmp/branch) ($(cat /tmp/commit))" \
290
#    && git checkout $(cat /tmp/branch) \
291
#    || git checkout $(cat /tmp/branch)
292
#
293
#    echo
294
#    echo "The documentation was committed to the 'pages' branch."
295
#    echo "You can now push it with"
296
#    echo "    git push origin pages"
297
#
298
pages:
299
  # Hardcoded values in the job:
300
  # (for Gitlab to understand that this job builds the Pages)
301
  # - the job name must be 'pages'
302
  # - the stage must be 'deploy'
303
  # - the artifacts must be a 'public' directory
304

    
305
  stage: deploy
306

    
307
  # no need for an OCaml environment
308
  image: alpine:latest
309

    
310
  # no cache
311

    
312
  # run this job only in the 'pages' branch
313
  rules:
314
    # https://docs.gitlab.com/ee/ci/yaml/#rules
315
    - if: '$CI_COMMIT_REF_NAME == "pages"'
316
      when: on_success
317
  artifacts:
318
    paths:
319
      - public
320
  script:
321
    - rm -fR public/
322
    - mkdir -p docs
323
    - cp -r docs public
324

    
325

    
326

    
327
## MIT License
328
#
329
# Copyright 2021 Gabriel Scherer
330
#
331
# Permission is hereby granted, free of charge, to any person obtaining
332
# a copy of this software and associated documentation files
333
# (the "Software"), to deal in the Software without restriction,
334
# including without limitation the rights to use, copy, modify, merge,
335
# publish, distribute, sublicense, and/or sell copies of the Software,
336
# and to permit persons to whom the Software is furnished to do so,
337
# subject to the following conditions:
338
#
339
# The above copyright notice and this permission notice shall be
340
# included in all copies or substantial portions of the Software.
341
#
342
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
343
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
344
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
345
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
346
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
347
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
348
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
(2-2/17)