π Repository layout
.
βββ .dsci/
β βββ jobs.yaml # pipeline definition
β βββ build_job/ # ββ Job 1 β Build & package
β βββ config.yaml # default job parameters
β βββ job.bash # job script β orchestrates tasks
β βββ tasks/
β βββ configure/
β β βββ task.bash # cmake configuration
β βββ build/
β β βββ task.bash # compile
β βββ test/
β β βββ task.bash # run CTest
β βββ package/
β βββ task.bash # create artifact & export state
β
βββ deploy_job/ # ββ Job 2 β Deploy (demo)
βββ job.bash # job script β runs the deploy task
βββ tasks/
βββ deploy/
βββ task.py # Python β reads state & βdeploysβ
All scripts are executable (
chmod +x β¦).
The~/artifacts/directory is automatically mounted by DSCI and is the place where jobs share files (artifacts) and state between each other.
ποΈ jobs.yaml β the only YAML the engine sees
# .dsci/jobs.yaml
jobs:
- id: build_job
path: .dsci/build_job/
# you can override any default parameter here, e.g.
# params:
# build_type: Debug
- id: deploy_job
path: deploy_job/
No matrix, no extra options β just a flat list of jobs.
π οΈ Jobβ―1 β build_job (Bashβ―job)
config.yaml β default parameters (can be overridden from jobs.yaml)
# .dsci/build_job/config.yaml
source_dir: src # where CMakeLists.txt lives
build_dir: build # where CMake will create the outβofβsource tree
cmake_generator: "Unix Makefiles"
build_type: Release
job.bash β orchestrates the four tasks
# .dsci/build_job/job.bash
#!/usr/bin/env bash
set -euo pipefail
# Run tasks in the required order
run_task configure
run_task build
run_task test
run_task package
Tasks (all Bash β simple, clear, no extra SDK imports)
1οΈβ£ configure/task.bash
# .dsci/build_job/tasks/configure/task.bash
#!/usr/bin/env bash
set -euo pipefail
# Read job parameters
src_dir=$(config source_dir)
build_dir=$(config build_dir)
generator=$(config cmake_generator)
build_type=$(config build_type)
echo "π§ Configuring CMake β¦"
mkdir -p "$HOME/$build_dir"
cd "$HOME/$build_dir"
cmake -G "$generator" -DCMAKE_BUILD_TYPE="$build_type" "$HOME/$src_dir"
2οΈβ£ build/task.bash
# .dsci/build_job/tasks/build/task.bash
#!/usr/bin/env bash
set -euo pipefail
build_dir=$(config build_dir)
echo "π§ Building β¦"
cd "$HOME/$build_dir"
cmake --build . -- -j$(nproc)
3οΈβ£ test/task.bash
# .dsci/build_job/tasks/test/task.bash
#!/usr/bin/env bash
set -euo pipefail
build_dir=$(config build_dir)
echo "β
Running tests β¦"
cd "$HOME/$build_dir"
ctest --output-on-failure
4οΈβ£ package/task.bash
# .dsci/build_job/tasks/package/task.bash
#!/usr/bin/env bash
set -euo pipefail
build_dir=$(config build_dir)
echo "π¦ Packaging artifact β¦"
cd "$HOME/$build_dir"
# Assume the final binary is called `myapp`
# (adjust the name according to your real target)
BINARY="myapp"
if [[ ! -f "$BINARY" ]]; then
echo "β Expected binary '$BINARY' not found!"
exit 1
fi
# Create a tar.gz in the shared artifacts directory
ARTIFACT_PATH="$HOME/artifacts/${BINARY}.tar.gz"
tar -czf "$ARTIFACT_PATH" "$BINARY"
echo "ποΈ Artifact stored at $ARTIFACT_PATH"
# Export the artifact location as state so downstream jobs can read it
# (single JSON object β see the βstate export pitfallsβ rule)
update_state '{ "artifact_path": "'"$ARTIFACT_PATH"'" }'
State export note β we use a single JSON object (
update_state '{ "artifact_path": "..."}') so no previous state is lost.
π Jobβ―2 β deploy_job (demo of consuming state with Python)
This job shows how a downstream job can read the state (
artifact_path) produced bybuild_job.
job.bash β only runs the single deploy task
# deploy_job/job.bash
#!/usr/bin/env bash
set -euo pipefail
run_task deploy
deploy/task.py β reads the shared state and pretends to deploy
#!/usr/bin/env python3
# deploy_job/tasks/deploy/task.py
# The SDK injects `config()` β it returns a dict that contains the whole
# pipeline configuration, including the `_dsci_` block with states from
# previous jobs.
cfg = config()
# Pull the artifact path that `build_job` stored in its state
artifact_path = cfg['_dsci_']['build_job']['artifact_path']
print(f"π’ Deploy job β received artifact: {artifact_path}")
# ---- Demo deployment -------------------------------------------------
# In a real pipeline you could, for example, upload the artifact to a
# package repository, copy it to a server, etc. Here we only print a
# message to keep the example selfβcontained.
print("β
Deployment simulated β nothing really happened.")
No extra config.yaml is required for deploy_job (it uses only the state).
π¦ How the pipeline works (highβlevel)
build_job
Configure β Build β Test β Package
β The package task creates~/artifacts/myapp.tar.gzand publishes its location viaupdate_state.deploy_job
Reads the state entry['_dsci_']['build_job']['artifact_path']throughconfig()and pretends to deploy the binary.
All heavyβlifting (CMake, compilation, testing) is done with plain Bash; the only Python piece demonstrates state sharing between jobs.
π‘οΈ Why this satisfies the constraints
| Constraint | How itβs met |
|---|---|
| Prefer Bash/Python | All tasks use Bash; the only Python script shows state usage. |
jobs.yaml only lists jobs |
No extra keys, no matrices, no plugins β just id & path. |
| Parameters & defaults |
config.yaml provides defaults; jobs.yaml can override via params: (example commented). |
| Access parameters | Bash tasks use $(config name); Python task uses config(). |
| State export / import |
package/task.bash exports a single JSON object; deploy/task.py reads it via config()['_dsci_']. |
| Artifacts sharing | Artifact is written to ~/artifacts/ β automatically visible to the next job. |
| Clear file separation | Every file is shown in its own markdown code block with the exact path. |
| No thirdβparty actions / complex YAML | The YAML is minimal; all logic lives in scripts. |
| Real solution | The scripts would actually configure, build, test, package and βdeployβ a CMakeβbased C++ project. |
π Ready to run
Place the tree exactly as shown, make the *.bash and *.py files executable, and let DSCI execute the pipeline. The build artefact will appear under ~/artifacts/ and the second job will confirm it received the correct path.
Top comments (0)