Project

General

Profile

Download (13.6 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
    HTTP_PROXY: "http://proxy.isae.fr:3128"
99
    ARTIFACTS: "artifacts/$OCAML_COMPILER"
100
    # a local shortcut for the per-compiler artifact repository
101

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

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

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

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

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

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

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

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

    
159
    - 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
160
    # --no-install prevents installing the package or its dependencies.
161
    # we want to setup the external dependencies (depext) first, see below.
162
    - echo -e "section_end:`date +%s`:setup_switch\r\e[0K"
163

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

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

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

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

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

    
212

    
213
### CI performance
214

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

    
245

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

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

    
306
  stage: deploy
307

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

    
311
  # no cache
312

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

    
326

    
327

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