From 04dc8c25d9e8d3965a369bd08f7d9d7d3bcf1cc2 Mon Sep 17 00:00:00 2001 From: Orestis <orestis.malaspinas@pm.me> Date: Wed, 20 Sep 2023 17:23:18 +0200 Subject: [PATCH] format --- Wiki/CLI-assignment-creation.md | 414 ++++++++++++++++++++++++++++++++ 1 file changed, 414 insertions(+) create mode 100644 Wiki/CLI-assignment-creation.md diff --git a/Wiki/CLI-assignment-creation.md b/Wiki/CLI-assignment-creation.md new file mode 100644 index 0000000..1644129 --- /dev/null +++ b/Wiki/CLI-assignment-creation.md @@ -0,0 +1,414 @@ +# How to create an assignment + +We will describe how to create a very simple assignment with dojo. + +Here we will build a `Hello world!` in the C programming language. We will describe how one must +modify the default template to have an exercise. + +The exercise will be to fill a function to return the `Hello world!` string. +The output of the function will be printed on the standard output. +The structure will be provided to the students as well as a very simple `Makefile` +to compile and run the code. + +The success or failure of this assignment will be tested by comparing the output +of our program with an the expected output (the famous `Hello world!`). + +To build this exercise we need to perform several steps that will be described below in great details. + +## Empty assignment creation + +First create a new assignment with the command +```bash +$ dojo assignment create --name c_hello_world +``` +```console +Please wait while we verify and retrieve data... +ℹ Checking Dojo session: + ✔ The session is valid + ✔ Teaching staff permissions +ℹ Checking Gitlab token: + ✔ Read access + ✔ Write access +✔ Assignment name "c_hello_world" is available +Please wait while we are creating the assignment... +✔ Assignment successfully created + ℹ Name: c_hello_world + ℹ Web URL: https://gitedu.hesge.ch/dojo/assignment/c_hello_world + ℹ HTTP Repo: https://gitedu.hesge.ch/dojo/assignment/c_hello_world.git + ℹ SSH Repo: ssh://git@ssh.hesge.ch:10572/dojo/assignment/c_hello_world.git +``` + +## Clone the assignment locally + +The assignment is nothing more than a git repository. We just clone it and start +modifying it +```bash +$ git clone ssh://git@ssh.hesge.ch:10572/dojo/assignment/c_hello_world.git +``` +```console +Cloning into 'c_hello_world'... +remote: Enumerating objects: 18, done. +remote: Counting objects: 100% (18/18), done. +remote: Compressing objects: 100% (12/12), done. +remote: Total 18 (delta 3), reused 18 (delta 3), pack-reused 0 +Receiving objects: 100% (18/18), 4.37 KiB | 4.37 MiB/s, done. +Resolving deltas: 100% (3/3), done. +``` +The `c_hello_world` assignment has now the following structure +```bash +$ tree c_hello_world/ +``` +```console +c_hello_world/ +├── docker-compose.yml +├── Dockerfile +├── dojo.assignment +└── README.md +``` + +## Build the development environment + +In order to execute a C program we need a working compiler and `make`. In order to +produce a special result file to be parsed by the `dojo` tool to produce nicely formatted output +we will use `jq`, a command-line json creation/edition/reding tool. Our `Dockerfile` +contains +```dockerfile +FROM ubuntu:latest +RUN apt update && apt install gcc make jq -y +COPY . / +ENTRYPOINT ["./run.sh"] +``` +We see that we start by installing the required packages on top of the latest Ubuntu image. +We then proceed to copy the complete assignment into the docker container, finall we run +a yet to be created `run.sh` script. + +We can test our `Dockerfile` by running the command +```bash +$ docker compose run --build hello_world +``` +```console +[+] Building 20.5s (8/8) FINISHED + => [hello_world internal] load build definition from Dockerfile + => => transferring dockerfile: 134B + => [hello_world internal] load .dockerignore + => => transferring context: 2B + => [hello_world internal] load metadata for docker.io/library/ubuntu:latest + => CACHED [hello_world 1/3] FROM docker.io/library/ubuntu:latest@sha256:ec050c32e4a6085b423d36ecd025c0d3ff00c38ab93a3d71a460ff1c44fa6d77 + => [hello_world internal] load build context + => => transferring context: 39.87kB + => [hello_world 2/3] RUN apt update && apt install gcc make jq -y + => [hello_world 3/3] COPY . / + => [hello_world] exporting to image + => => exporting layers + => => writing image sha256:4b561113c7123da08206a2cf2642cb4f331670fe44350646437eaa78e44aff3a + => => naming to docker.io/library/c_hello_world-hello_world +ERRO[0021] error waiting for container: +Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "./run.sh": stat ./run.sh: no such file or directory: unknown +``` +We see an error at the end of the command which is perfectly normal. We did not create the `run.sh` file et (let's leave that for later). + +We then need to focus on the `docker-compose.yml` file which will take care of all the hard work +needed if complex workflows are required. Here it is as simple as possible and should contain +```yml +services: + hello_world: + container_name: hello_world + build: + context: ./ + dockerfile: Dockerfile + volumes: + - hello_world_volume:/result # <hello_world_volume> must be the same as below but + # the name may be arbitrary. This volume must be + # present in the dojo_assignment.json file under the field + # "result": { + # "volume": "hello_world_volume", + # ... + # } +volumes: + hello_world_volume: +``` +In this file, we see the definition of a `hello_world_volume` this is an arbitrary name and can be changed but it +must be coherent in this file and in the `dojo_assignment.json` file (more on this later configuration file in [The dojo configuration file](#the-dojo-configuration-file)). +This volume is responsible of mounting `/result/` directory which will contain all +the output generated by our assignment (again the name can be changed). In particular +in this director e will be required to create a `dojo_assignment.json` file +that contains at least if the assignment was successfully performed (more on that at the end of the [Creating the assignment files](#creating-the-assignment-files) section). + +## The dojo configuration file + +The `dojo_assignment.json` file contains the general configuration of the assignment. +The important configuration parameters are: +- the *immutable files* which are files that will be overwritten when the compilation pipeline is run (even if the student +modifies these files the modifications will not be taken into account), +- the *results* which is the volume corresponding to the `docker-compose.yml` file. + +In its default form `dojo_assignment.json` file contains +```json +{ + "dojoAssignmentVersion": 1, + "version": 1, + "immutable": [ + { + "description": "Dockerfile of the unique container", + "path": "Dockerfile", + "isDirectory": false + } + ], + "result": { + "container": "hello_world", + "volume": "hello_world_volume" + } +} +``` +Here we see only one immutable file which is the `Dockerfile` (the `isDirectory` field is `false` but it is possible to make +complete directories immutable) and we see that: +* the value of the `container` field (`hello_world`) corresponds to +the value of the `container_name` field in the `docker-compose.yml` file, +* the value of the `volume` field (`hello_world_volume`) corresponds to the `volumes` field in the `docker-compose.yml` file. + +This file will be completed in [The immutable files](#the-immutable-files) sectino with the files of our assignment that will be +created in the [next section](#creating-the-assignment-files). + +## Creating the assignment files + +For this assignment we will create the following files with the following content: + +- `src/Makefile` +```makefile +CC:=gcc +CFLAGS:=-Wall -Wextra -Wpedantic -fsanitize=address -Werror -g +LDFLAGS:=-fsanitize=address + +hello_world: hello_world.o function.o + gcc -o $@ $^ $(LDFLAGS) + +hello_world.o: function.h + +function.o: function.h + +run: hello_world + ./hello_world + +clean: + rm -f *.o hello_world +``` +- `src/hello_world.c` +```c +#include <stdio.h> +#include <stdlib.h> +#include "function.h" + +int main() +{ + printf("%s", hello_world()); + return EXIT_SUCCESS; +} +``` +- `src/function.h` +```c +#ifndef _FUNCTION_H_ +#define _FUNCTION_H_ + +char *hello_world(); + +#endif +``` +- `src/function.c` +```c +#include "function.h" + +char *hello_world() +{ + // TODO: Replace 0 here to return "Hello world!" + return 0; +} +``` +- `src/expected_output.txt` +``` +Hello world! +``` + +These files will be used to create an executable that will be run by the custom execution script, `run.sh`, see the `Dockerfile` in +the [Build the environment](#build-the-development-environment) section. + +All these files have arbitrary names and it's completely up to the teacher to make a coherent exercise. + +The only missing file is `run.sh` (do not forget to make it executable, `chmod +x run.sh`) that contains the following code +```bash +#!/bin/bash + +echo "Starting tests." + +GLOBAL_SUCCESS=false + +make -C src clean -s +make -C src -s +if [ $? -eq 0 ]; then + make run -C src > src/output.txt -s + if [ $? -eq 0 ]; then + diff --color src/output.txt src/expected_output.txt > result/diff_output.txt + if [ $? -ne 0 ]; then + echo "Output is wrong:"; + cat result/diff_output.txt + else + echo "All tests were a complete success" + GLOBAL_SUCCESS=true + fi + + else + echo "Execution failed"; + fi +else + echo "Compilation failed." +fi + +jq --null-input --arg success $GLOBAL_SUCCESS \ + '{"success": $success | test("true")}' > /result/results.json +``` +Here one can see the creation of two different files that are located in the `result` directory: +- `result/results.json` +- `result/diff_output.txt` +The `results.json` is mandatory to be created and must at least contain the +`success` field must be `true` or `false` and determines whether the +assignment is a success or a failure. The other files present in the `result` folder +can be retrieved by the students. + +To test if everything works according to plan, one can again use the command +```bash +$ docker compose run --build hello_world +``` +```console +[+] Building 1.8s (8/8) FINISHED + => [hello_world internal] load build definition from Dockerfile + => => transferring dockerfile: 134B + => [hello_world internal] load .dockerignore + => => transferring context: 2B + => [hello_world internal] load metadata for docker.io/library/ubuntu:latest + => [hello_world 1/3] FROM docker.io/library/ubuntu:latest@sha256:ec050c32e4a6085b423d36ecd025c0d3ff00c38ab93 + => [hello_world internal] load build context + => => transferring context: 3.23kB + => CACHED [hello_world 2/3] RUN apt update && apt install gcc make jq -y + => [hello_world 3/3] COPY . / + => [hello_world] exporting to image + => => exporting layers + => => writing image sha256:5499aa3aa0b2f2021584dbf46b4b05ab83dc0a5ad4d6c81a3466c56673c3f562 + => => naming to docker.io/library/c_hello_world-hello_world +Starting tests. +Output is wrong: +1c1 +< (null) +\ No newline at end of file +--- +> Hello world! +\ No newline at end of file +``` +where we see that the execution fails. This will be improved in the future and the actual execution as expected to be +run by the students will be added. + +## The immutable files + +There is only one file that should be modified by the students in this example: the `function.c` file. Therefore we can safely +add all the created files in the `src` except `src/function.c` which is precisely +the file that the student must modify, as well as the `run.sh` file. The `dojo_assignment.json` file becomes +```json +{ + "dojoAssignmentVersion": 1, + "version": 1, + "immutable": [ + { + "description": "Dockerfile of the unique container", + "path": "Dockerfile", + "isDirectory": false + }, + { + "description": "The entry point of the Dockerfile", + "path": "run.sh", + "isDirectory": false + }, + { + "description": "The makefile for compilation/execution purposes", + "path": "src/Makefile", + "isDirectory": false + }, + { + "description": "Entry point of the code", + "path": "src/hello_world.c", + "isDirectory": false + }, + { + "description": "The header file fot the program's assignment", + "path": "src/function.h", + "isDirectory": false + }, + { + "description": "The expected output file for comparing with the actual output", + "path": "src/expected_output.txt", + "isDirectory": false + } + ], + "result": { + "container": "hello_world", + "volume": "hello_world_volume" + } +} +``` + +## The `README.md` file + +The `README.md` file is used to provide informations on the assignment and the way the teacher wants students to accomplish +the assignment and its content is completely free. It is recommended to use the `Markdown` syntax as the +file extension suggests. + +In this assignment the `README.md` file reads +```markdown +# Hello world! + +C'est le premier vrai exercice jamais créé sur le Dojo! + +Il sert de tutoriel pour créer un exercice simple en C. + +## But + +Le but est de faire afficher "Hello world!" à notre programme en C. + +Pour ce faire, il faut modifier le fichier `src/function.c` sous la ligne +annotée avec `TODO`. Bonne chance! +``` + +## Publish the work + +Now that the assignment is ready, we must publish it. First add/commit/push all the +files needed for your exercise. In this case, it should be: +```console +c_hello_world/ +├── docker-compose.yml +├── Dockerfile +├── dojo_assignment.json +├── README.md +├── run.sh +└── src + ├── expected_output.txt + ├── function.c + ├── function.h + ├── hello_world.c + └── Makefile +``` +Then one must *publish* the assignment for the students to be able to perform to get the exercise. + +```bash +$ dojo assignment publish c_hello_world +``` +```console +? Are you sure you want to publish this assignment? Yes +Please wait while we verify and retrieve data... +ℹ Checking Dojo session: + ✔ The session is valid +ℹ Checking assignment: + ℹ c_hello_world + ✔ The assignment exists + ✔ You are in the staff of this assignment +Please wait while we publish the assignment... +✔ Assignment c_hello_world successfully published +``` + +The assignment is now ready to be performed by students! -- GitLab