Génération de code › Régénérer le code

Génération de code

Cette page n’est pas encore traduite : voici la version anglaise.

Regenerating code

archec gen writes the code again each time the model changes. This page is about what happens to the code you wrote around it, how a line of generated code leads back to the model, and how to see a regeneration before it happens.

Your code: USER CODE regions#

Generated C, Rust and Go mark the places where hand-written code goes:

/* @arche Brake.calibrate main.arche:42 fn */
void brake_calibrate(brake_t *self)
{
    /* USER CODE BEGIN Brake.calibrate.body */
    self->offset = adc_read(3);
    /* USER CODE END Brake.calibrate.body */
}

(// USER CODE BEGIN … in Rust and Go.) When archec writes the file again, it reads the file on disk first and puts back what is between the markers of each id. The ids are model paths (Brake.calibrate.body, Brake.init, car.brake_fw.start), not line numbers, so the code around a region can move, grow or shrink and your code still lands in its place.

Where the regions are:

Generator Regions
c, c/<part>.c <Part>.includes after the includes; <Part>.init at the end of <part>_init; <Part>.<fn>.body for a function whose body the model does not give (none, or Python the action language cannot say); <Part>.<port>.on-receive, <Part>.<port>.on-deadline likewise
c, c/<part>_ports.c <Part>.ports.includes; <Part>.<port>.send.<MESSAGE>, the body of each send function
c, c/<binary>/wiring.c <deployment>.<binary>.includes; <deployment>.<binary>.start, in <binary>_start once the parts are initialised and before the threads start
ros2, ros2_nodes/<binary>/src/<exe>.cpp <deployment>.<binary>.<exe>.includes after the includes; <part>.node at the end of each node's constructor; <part>.members in its class; <deployment>.<binary>.<exe>.start in main, once the nodes are made and before the threads start
ros2, ros2_nodes/<binary>/CMakeLists.txt <deployment>.<binary>.cmake before ament_package()
ros2, ros2_nodes/<binary>/c/<part>.c the c target's: <Part>.includes, <Part>.init, …
go, parts.go <Part>.init in New<Part>; <Part>.<fn>.body for a function the model gives no body
embassy, src/app.rs <part path>.<fn>.body for a function with no body; each guard to implement; each input handler (<part path>.on_<port>)

A body the model gives (in the action language, or Python it can translate) is generated, not a region: change it in the model.

When a region disappears. If the model no longer gives a region — the function was removed or renamed — the code that was in it is not dropped: it is added to <file>.orphans beside the file (for example generated/c/brake.c.orphans), under the region's id, the date and the line it was at. Move what you still need into a region of the new code and delete the file; it is not compiled. A region that still holds only what archec put there (its stub) is not an orphan.

A file of yours with regions. Some files are yours, written once and never again (keep): c/<part>_ports.c, src/app.rs. When such a file has regions and nothing outside them changed since archec wrote it, archec writes it again around your regions, so a new port or a new function reaches it. As soon as you edit outside the regions, the file is yours as before and left as it is (archec gen says so; delete it for a fresh one). An older file with no regions is never touched.

Your own generators get the same: in a template,

{{#region "{{ id }}.glue"}}
    todo!()
{{/region}}

writes the markers around the default code, in the comment style of the file's name (/* */, // for .rs and .go, # for scripts and Makefiles), or the one a second argument names ("c", "line", "hash"). A script calls ctx.region(id, defaultCode, style, indent). See generators.

Regions in another generator's markers. The style "user-code" writes the markers // -- start of user code: <name> … // -- end of user code, so that code written in files another generator wrote that way is carried into the new ones:

// -- start of user code: additional members
int m_count = 0;
// -- end of user code

# -- in a .txt or CMake file, <!-- start of user code: name--> in XML (by the file's name; "user-code-hash" and "user-code-xml" say it). The region's name may hold spaces and the end marker names nothing; archec reads them on regeneration like its own, by name: write {{#region "additional members" "user-code"}} in a template, or ctx.region("additional members", "", "user-code", "", "Core.h") in a script, with the name the other generator gave it, and the code in the file on disk comes back there.

From the code to the model#

Each generated function or type that comes from a model element has a tag:

/* @arche Brake.calibrate main.arche:42 fn */

the element's path, its file (relative to the project) and line, and its kind (component, fn, init, machine, on-receive, send, thread, binary, part). From the tags, archec gen writes generated/.archec/genmap.json: for each file, the line ranges of each element (from its tag to where its braces close), and what it needs to carry the regions (a hash of the stubs and of the file outside its regions).

archec where reads it to put a compiler's error back in the model:

$ make -C generated/c/brake_fw 2>&1 | archec where main.arche --from generated/c/brake_fw -
../brake.c:17:5: error: use of undeclared identifier 'adc_read'
  ↳ main.arche:42: Brake.calibrate (fn), in your USER CODE region Brake.calibrate.body
$ archec where main.arche "generated/c/brake.c:17:5: error: …"
main.arche:42  Brake.calibrate (fn) ← generated/c/brake.c:17, in your USER CODE region Brake.calibrate.body

It reads gcc, clang and Go (file:line:col:), rustc (--> file:line:col) and MSVC (file(line)). A path is resolved from where the compiler ran (--from, else here), then by its end against the map in generated/ (or --gen <dir>).

In VS Code, a generated file with tags has a Go to model element lens above each tagged function (and the command of that name, which uses the cursor); on an element of a .arche file, Show generated code opens the code generated from it.

Before writing: --diff and the preview#

archec gen main.arche --target c --diff

prints a unified diff of what a write would change — with your regions carried, exactly as the write would — writes nothing, and exits 1 when something would change (0 when nothing would). --check does the same and lists the files instead of the diff; both fit CI. A region that would become an orphan is said too.

In VS Code, Arche: Preview Generated Changes… opens VS Code's diff editor for each file that would change, then asks whether to write them.

Limits#

  • A tag carries the element's line, so a model edit above it changes the generated file (its tags), even when the code is the same.
  • The ranges in the map follow braces: an element written without braces (a declaration) ends at its ;; a brace inside a string with an escaped quote can throw the count off.
  • embassy's src/arche.rs and the Go main.go have no regions: they are the generator's alone. Files written once that predate regions (main.c, drivers.c, user.go) stay yours and are never rewritten.

Renaming an element#

A region is named after its element, so a rename changes its name: BrakeEcu.on_cmd.body becomes BrakeEcu.on_command.body. Your code still follows the element.

  • With the rename tools, the code is carried exactly. These are the editor's Rename (F2) and archec refactor rename main.arche BrakeEcu.on_cmd on_command.
    • They record the rename in arche.renames.json beside the model. Commit it, as you commit arche.lock.
    • The next archec gen applies each entry once per output folder. The genmap remembers the last one it applied.
    • A component renamed writes another file (brake.c → brake_unit.c), and the code follows it there. The old file is said to be no longer generated: delete it.
  • By hand, a rename is recognised when it is clear: exactly one region with code is gone and exactly one new region takes its place, with the same name but for one part and the same stub from the generator. archec gen says so: Brake.hold.body → Brake.clamp.body (your code carried: looked like a rename).
  • Anything else stops archec gen. That covers two functions of the same signature renamed at once, and an element removed.
    • Nothing is written, and the exit code is 1. --check and --diff stop the same way, so CI sees it.
    • The output names each stranded region and the new regions it may belong to. Then either:
      • --move Brake.apply.body=Brake.press.body (repeatable) puts the code where it belongs;
      • --orphans ok lets it go to <file>.orphans, as before.
    • In VS Code, Preview Generated Changes asks the same question for each stranded region.
    • archec check warns before that (gen-stranded) while you edit the model: it reads generated/ beside it.