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
|
- if [ "$CLEAN_OPAM_CACHE" == "true" ]; then echo "we clean the _opam cache as explicitly requested"; rm -fR _opam; fi
|
145
|
- if [ "$CLEAN_DUNE_CACHE" == "true" ]; then echo "we clean the dune _build cache as explicitly requested"; rm -fR _build; fi
|
146
|
# Note: Gitlab supports multi-line scripts with "- |" or "- >", but the
|
147
|
# display in the logs does not show the whole script being run, reducing
|
148
|
# understandability. We only use multi-line scripts for "echo" line,
|
149
|
# and otherwise keep long one-liners.
|
150
|
# https://docs.gitlab.com/ee/ci/yaml/script.html#split-long-commands
|
151
|
|
152
|
# see https://docs.gitlab.com/ee/ci/jobs/index.html#custom-collapsible-sections
|
153
|
# to understand the "section_{start,end}:`date +%s`:<name>\r\e[0K human-readable text" pattern
|
154
|
- echo -e "section_start:`date +%s`:setup_switch\r\e[0K setup opam switch"
|
155
|
- if [ -d _opam ]; then echo "we reuse the local opam switch from the CI cache"; fi
|
156
|
|
157
|
- 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
|
158
|
# --no-install prevents installing the package or its dependencies.
|
159
|
# we want to setup the external dependencies (depext) first, see below.
|
160
|
- echo -e "section_end:`date +%s`:setup_switch\r\e[0K"
|
161
|
|
162
|
- echo -e "section_start:`date +%s`:setup_deps\r\e[0K setup the package dependencies"
|
163
|
# (Note: when opam 2.1 will be available with native depext integration,
|
164
|
# the complex dance below will be replaceable by just
|
165
|
# opam install . --deps-only --locked --with-test --with-doc --yes
|
166
|
# which should start by installing depexts
|
167
|
# (possibly we will need to set some explicit environment variable first).
|
168
|
- opam install depext --yes
|
169
|
- opam install . --dry-run --deps-only --locked --with-test --with-doc --yes | awk '/-> installed/{print $3}' | xargs opam depext -iy
|
170
|
# the magical command above comes from https://github.com/ocaml/opam/issues/3790
|
171
|
# it installs both external dependencies and opam dependencies
|
172
|
- echo "(we used --locked to honor any lockfiles if present)"
|
173
|
- echo -e "section_end:`date +%s`:setup_deps\r\e[0K"
|
174
|
|
175
|
- echo -e "section_start:`date +%s`:project_build\r\e[0K build the project"
|
176
|
- eval $(opam env)
|
177
|
- if [ "$DUNE_BUILD_TARGETS" != "" ]; then dune build $DUNE_BUILD_TARGETS --display short; else echo "skipped (DUNE_BUILD_TARGETS is empty)"; fi
|
178
|
- echo -e "section_end:`date +%s`:project_build\r\e[0K"
|
179
|
|
180
|
- echo -e "section_start:`date +%s`:project_tests\r\e[0K run the tests"
|
181
|
- if [ "$DUNE_TEST_TARGETS" != "" ]; then dune build $DUNE_TEST_TARGETS --display short; else echo "skipped (DUNE_TEST_TARGETS is empty)"; fi
|
182
|
- echo -e "section_end:`date +%s`:project_tests\r\e[0K"
|
183
|
|
184
|
- echo -e "section_start:`date +%s`:project_doc\r\e[0K build the documentation"
|
185
|
- if [ "$DUNE_DOC_TARGETS" != "" ]; then dune build $DUNE_DOC_TARGETS --display short; else echo "skipped (DUNE_DOC_TARGETS is empty)"; fi
|
186
|
- echo -e "section_end:`date +%s`:project_doc\r\e[0K"
|
187
|
|
188
|
- echo -e "section_start:`date +%s`:artifacts\r\e[0K populating the artifacts"
|
189
|
- mkdir -p $ARTIFACTS
|
190
|
- >
|
191
|
echo "Build artifacts will be available at
|
192
|
$CI_JOB_URL/artifacts/browse/$ARTIFACTS
|
193
|
Note: by default Gitlab only keeps them for a few weeks."
|
194
|
- >
|
195
|
if [ "Dune_DOC_TARGETS" != "" ]; then
|
196
|
cp -r _build/default/_doc/_html $ARTIFACTS/doc;
|
197
|
echo "Documentation:
|
198
|
$CI_JOB_URL/artifacts/browse/$ARTIFACTS/doc/index.html";
|
199
|
fi
|
200
|
- >
|
201
|
if [ -f _build/log ];
|
202
|
then
|
203
|
mkdir -p $ARTIFACTS/_build;
|
204
|
cp _build/log $ARTIFACTS/_build/log
|
205
|
echo "Dune build log:
|
206
|
$CI_JOB_URL/artifacts/browse/$ARTIFACTS/_build/log";
|
207
|
fi
|
208
|
- echo -e "section_end:`date +%s`:artifacts\r\e[0K"
|
209
|
|
210
|
|
211
|
### CI performance
|
212
|
|
213
|
# Performance analysis of one 'build' job on a small, simple library project
|
214
|
# (the project build/test/doc time is basically neglectible) on gitlab.com:
|
215
|
#
|
216
|
# (To get those numbers for your project, just look at a passing build and fold all
|
217
|
# log sections.)
|
218
|
#
|
219
|
# 2m total
|
220
|
# 56s preparing the docker image and CI environment
|
221
|
# 14s restoring the _opam cache
|
222
|
# 10s installing the project dependencies
|
223
|
# (external dependencies are not cached, opam dependencies are cached)
|
224
|
# 3s project build, tests, doc
|
225
|
# 31s saving the _opam cache
|
226
|
# 6s uploading artifacts
|
227
|
#
|
228
|
# When running with CLEAN_OPAM_CACHE=true, the costs are similar, except for
|
229
|
# 7m30s building the local opam switch
|
230
|
# 2m07s building the project dependencies (external and opam)
|
231
|
# This shows that caching the local _opam switch is very important, as
|
232
|
# its setup time dominates the build (for a small project).
|
233
|
#
|
234
|
# Another thing that is clear from these numbers is that splitting
|
235
|
# your CI in several jobs could be fairly expensive: for each job you
|
236
|
# pay 56s seconds of docker setup, plus 14s to restore the _opam
|
237
|
# cache. (The 31s to save the cache afterwards can be avoided by using
|
238
|
# a "policy: pull" setting if the job uses the _opam in a read-only
|
239
|
# way.)
|
240
|
#
|
241
|
# This is why this CI script does everything in a single 'build' step.
|
242
|
|
243
|
|
244
|
### Deploy stage
|
245
|
#
|
246
|
# pushing some project artifacts to the web.
|
247
|
|
248
|
# The "pages" rule uploads a custom website whenever someones
|
249
|
# pushes a commit to the "pages" branch of the repository.
|
250
|
#
|
251
|
# The content of the website is the sub-filesystem
|
252
|
# located in the "docs/" subdirectory of the branch index.
|
253
|
#
|
254
|
# This behavior emulates the way per-repository Github pages work.
|
255
|
#
|
256
|
# If the repository is at
|
257
|
# https://gitlab.com/<group>/<project>,
|
258
|
# then the published website will be at
|
259
|
# https://<group>.gitlab.io/<project>
|
260
|
# In general see
|
261
|
# https://docs.gitlab.com/ee/user/project/pages/getting_started_part_one.html#gitlab-pages-default-domain-names
|
262
|
#
|
263
|
# We previously used a setup where the documentation was pushed to
|
264
|
# Gitlab Pages automatically when pushing a tag (a new release). This
|
265
|
# was much less flexible, with no easy way for users to publish web
|
266
|
# content *other* than the documentation, or to control when the
|
267
|
# content should be refreshed.
|
268
|
#
|
269
|
# It is fairly simple to publish the documentation
|
270
|
# from any branch with the current interface.
|
271
|
# Here is an example script:
|
272
|
#
|
273
|
# # compute the current commit hash and branch name
|
274
|
# git rev-parse --short HEAD > /tmp/commit
|
275
|
# git rev-parse --abbrev-ref HEAD > /tmp/branch
|
276
|
# cat .gitlab-ci.yml /tmp
|
277
|
#
|
278
|
# # move to the 'pages' branch and commit the documentation there
|
279
|
# git checkout pages
|
280
|
# mkdir -p docs
|
281
|
# git rm --ignore-unmatch -r docs > /dev/null
|
282
|
# cp -r ${DOC_PATH} docs
|
283
|
# git add docs
|
284
|
# # we ensure that the CI configuration is present on the 'pages' branch,
|
285
|
# # otherwise pushing from there may not deploy as expected.
|
286
|
# cp /tmp/.gitlab-ci.yml .
|
287
|
# git add .gitlab-ci.yml
|
288
|
# git commit -m "documentation for $(cat /tmp/branch) ($(cat /tmp/commit))" \
|
289
|
# && git checkout $(cat /tmp/branch) \
|
290
|
# || git checkout $(cat /tmp/branch)
|
291
|
#
|
292
|
# echo
|
293
|
# echo "The documentation was committed to the 'pages' branch."
|
294
|
# echo "You can now push it with"
|
295
|
# echo " git push origin pages"
|
296
|
#
|
297
|
pages:
|
298
|
# Hardcoded values in the job:
|
299
|
# (for Gitlab to understand that this job builds the Pages)
|
300
|
# - the job name must be 'pages'
|
301
|
# - the stage must be 'deploy'
|
302
|
# - the artifacts must be a 'public' directory
|
303
|
|
304
|
stage: deploy
|
305
|
|
306
|
# no need for an OCaml environment
|
307
|
image: alpine:latest
|
308
|
|
309
|
# no cache
|
310
|
|
311
|
# run this job only in the 'pages' branch
|
312
|
rules:
|
313
|
# https://docs.gitlab.com/ee/ci/yaml/#rules
|
314
|
- if: '$CI_COMMIT_REF_NAME == "pages"'
|
315
|
when: on_success
|
316
|
artifacts:
|
317
|
paths:
|
318
|
- public
|
319
|
script:
|
320
|
- rm -fR public/
|
321
|
- mkdir -p docs
|
322
|
- cp -r docs public
|
323
|
|
324
|
|
325
|
|
326
|
## MIT License
|
327
|
#
|
328
|
# Copyright 2021 Gabriel Scherer
|
329
|
#
|
330
|
# Permission is hereby granted, free of charge, to any person obtaining
|
331
|
# a copy of this software and associated documentation files
|
332
|
# (the "Software"), to deal in the Software without restriction,
|
333
|
# including without limitation the rights to use, copy, modify, merge,
|
334
|
# publish, distribute, sublicense, and/or sell copies of the Software,
|
335
|
# and to permit persons to whom the Software is furnished to do so,
|
336
|
# subject to the following conditions:
|
337
|
#
|
338
|
# The above copyright notice and this permission notice shall be
|
339
|
# included in all copies or substantial portions of the Software.
|
340
|
#
|
341
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
342
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
343
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
344
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
345
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
346
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
347
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|