Encoding complex build steps within .travis.yml is a real pain.
For simple setups one can certainly write a short one-liner in the before_install
section (or whichever phase fits best), but the moment these get longer than a “if X; then Y; else Z;” the problems begin. The documentation rightfully suggests to move complex scripts into files, which would be delivered together with the .travis.yml file.
The down-side of this approach is of course that one now needs to remember that the build configuration is not just a single file. This can be solved by using naming conventions (a .travis directory, for example).
Unfortunately this breaks when trying to avoid duplication of build configuration and the mess that comes with keeping these duplicates in-sync. Config importing allows to share the .travis.yml file (snippets) from another repository, but there is no way to say “and also please take these scripts and put them over here”.
I tried yesterday:
- “Somehow” do a
svn cat
/git cat-file
/… from the configuration repository. This fails because authentication. - “Somehow” use a here-document in a before_install step that produces a script. This fails because Travis seems to do weird things for each of these lines, so a here-document didn’t work (the end-marker ended up in the file???). The quoting is also odd, it seems one needs to escape ‘$’ in the before_install steps? From errors I fear there is some eval going on there
- Try to encode the script as a multiline yaml environment variable. This breaks in really funny ways: A regular yaml parser showed that the value was correct, but travis nonetheless managed to split the value on line-breaks and then did an export for each of the lines of my script.
I ended up giving up, and added a manual step into my shared repository: Whenever I modify a script, I have to run a conversion that produces a base64 encoded form of the script, and then encodes that into a template .yml file that travis can import and that dumps the script.
#!/bin/sh
mkdir -p generated
for script in scripts/*.sh; do
short_name=$(basename "${script}" .sh)
name=$(echo "${short_name}" | tr "[:lower:]/-" "[:upper:]__")
encoded=$(base64 < "${script}")
cat <<EOF >"generated/${short_name}.yml"
#
# Generated for ${script}
#
before_install:
- echo "\${${name}}" | base64 -d > "\${HOME}/.local/bin/${short_name}"
- chmod +x "\${HOME}/.local/bin/${short_name}"
env:
global:
- ${name}=${encoded}
EOF
done
Note that I have to do this manually: Travis’ import feature pulls from the source repository, which smells like it is repeating the mistake of bower and other systems that don’t understand the different between “sources” and “generated content”. The only way to automate this from what I can see would be either to distribute pre-commit hooks to developers of the configuration repository (which is manual!), or to have a Travis job on the configuration repository which then publishes the generated content into a different branch of the repository. Likely this will produce more mess …
What I would love to have:
- Allow me to create files directly from travis.yml:
files:- target: /tmp/foo.sh
mode: 0777
source: |-
#!/bin/sh
my script here
- target: /tmp/foo.sh
- Document how generated artifacts would work for travis imports so at least not every developer has to reinvent the wheel
- Document that imports need to have a .yml extension (without that I got odd errors, didn’t capture them though)
Other than that: The imports is definitely a step in the right direction, and it ultimately allowed me to clean up about 40 repositories with duplicated-but-slightly different build configurations of ~100 lines, and reduce that to a rather simple “import:” + “env:” structure.
Thanks for that!