Native modern-Fortran runner API¶
For authors writing a workflow runner in Fortran. The Fortran SDK is the same
authoring surface as the Python, Bash, and C ones. It adds no new bridge
protocol: it is iso_c_binding bindings over the native C library
(native/c/httk_workflow.{h,c}) plus one idiomatic Fortran module, so the C
httk_workflow_main still owns registration, dispatch, and the process exit
status, and every verb still execs $HTTK_WORKFLOW_PYTHON -m httk.workflow._shell_bridge <verb> …. A Fortran runner therefore publishes the
same bytes as a Python, Bash, or C runner for the same campaign. Only the
--describe handshake is native. The normative cross-language semantics are the
table in Python and Bash authoring parity; the function-by-function Fortran mapping is the table
below.
The SDK is one module source, native/fortran/httk_workflow.f90, packaged under
httk.workflow. It is modern Fortran (2008), warning-clean under gfortran -std=f2008 -Wall -Wextra -Werror, with no dependency beyond iso_c_binding and
the C SDK it wraps. It is designed to be compiled beside a runner together
with the C source.
A complete runner¶
A runner declares its workflow and its complete step set once, implements one
handler per step, and hands control to httk_workflow_main. Each handler is a
bind(c) function of no arguments returning integer(c_int):
module my_steps
use, intrinsic :: iso_c_binding, only: c_int
use httk_workflow
implicit none
contains
function step_prepare() result(code) bind(c)
integer(c_int) :: code
call ignore(httk_workflow_advance("run"))
code = 0
end function
function step_run() result(code) bind(c)
integer(c_int) :: code
call ignore(httk_workflow_succeed())
code = 0
end function
end module
program main
use, intrinsic :: iso_c_binding, only: c_funloc
use httk_workflow
use my_steps
implicit none
if (httk_workflow_runner("my.workflow", &
[character(len=8) :: "prepare", "run"], &
[c_funloc(step_prepare), c_funloc(step_run)]) /= HTTK_WORKFLOW_OK) &
call httk_workflow_exit(2)
call httk_workflow_exit(httk_workflow_main())
end program
Build it against the packaged SDK, compiling the two languages separately (the Fortran standard flag is not valid for C, and the C source is compiled with a C compiler, then linked into the Fortran build):
cc -std=c99 -c .../httk/workflow/native/c/httk_workflow.c -o httk_workflow_c.o
gfortran -std=f2008 .../httk/workflow/native/fortran/httk_workflow.f90 \
runner.f90 httk_workflow_c.o -o runner
The compiled binary is the runner file a job references. It is executable, so the manager runs it directly and the scaffolder describes it by running it — a job that starts from a runner file of your own is resolved the same way whatever language wrote it.
A complete VASP relaxation authored this way — prepare, run, publish, the
vasp.command setting, mock-VASP compatible, publishing to transactional data —
ships as examples/relax_fortran/; see the walkthrough at the end of this page.
Step handlers and dispatch¶
httk_workflow_runner(workflow, names, handlers) declares the complete step set
before any work happens. names is a character array of the step names, and
handlers is the array of their addresses, obtained with c_funloc, in the same
order. A name that is empty, contains a character outside [A-Za-z0-9._-], is
duplicated, or is paired with a null handler is refused by the C validator with a
diagnostic on stderr and the return value HTTK_WORKFLOW_REFUSED. The
registration is saved module state the C library keeps a pointer into; it
outlives the process, so nothing is copied on dispatch.
A step handler is a bind(c) function returning integer(c_int), not the
plainer Fortran subroutine, and this is the one place the Fortran surface is
shaped by the C ABI it sits on. The C dispatcher calls each handler through an
int (*)(void) pointer: c_funloc requires an interoperable (that is,
bind(c)) target, and reading the return value of a void-returning procedure
through an int (*)(void) pointer would be undefined behaviour. The return value
carries the same meaning as a C handler’s:
Ending |
Published outcome |
|---|---|
the handler publishes one, then |
that outcome |
the handler sets |
|
the step is not registered |
|
the handler sets |
an |
httk_workflow_main() forwards this process’s command line to the C
httk_workflow_main, which reads the step the manager asked for, dispatches its
handler, and owns the exit status. Because a Fortran program end always exits
0, the runner propagates that status with httk_workflow_exit(status) — the
Fortran counterpart of a C runner’s return from main (it calls libc exit,
so the code is exact and carries no STOP diagnostic, and the SDK stays within
Fortran 2008).
Because dispatch lives in the C library, the breadcrumb an aborted handler leaves
carries the exception label CError (not a Fortran-specific name). A Fortran
author who set code nonzero recognizes their handler in httk workflow job why
output by that CError exception together with the "<step> exited with status N" message.
HTTK_WORKFLOW_DESCRIBE=1 makes httk_workflow_runner print the runner
description and exit 0 before any step runs, and httk_workflow_main honours
--describe in argv the same way. The description is produced natively,
byte-for-byte what a Python, Bash, or C runner prints for the same workflow and
steps:
{"format": "httk-workflow-runner-description", "format_version": 1, "steps": ["prepare", "run"], "workflow": "my.workflow"}
Strings and ownership across the C boundary¶
Fortran callers never see a C pointer. The module marshals strings both ways and frees every C allocation exactly once:
Arguments in are ordinary
character(len=*)values, each copied to a NUL-terminated C string for the call. A trailing run of blanks is trimmed at the C boundary. This is a real divergence to be aware of, not a free lunch: a deferred-length string can hold meaningful trailing spaces, and they will not round-trip through this SDK. A value whose trailing whitespace is significant (a rare case for the bridge’s tokens and paths) must be passed through the C SDK instead, which copies bytes verbatim.Reads out are subroutines with a
character(len=:), allocatable, intent(out)result argument — deliberately not functions. The C side returns a freshlymalloc’d string; the module copies it into the argument and calls the Cfree, leaving a plain owned Fortran string with no cleanup for the caller. When the answer is absent (the C side returnedNULL), the argument is left unallocated, whichallocated(value)and thestatusargument both report; a legitimate empty string arrives allocated with length zero. That absent-versus-empty distinction is why the reads cannot be functions: an unallocated allocatable function result is undefined to assign from, and gfortran collapses it into a zero-length string, erasing the difference.Optional arguments carry the C tail-arguments and defaults. A read with an optional default (
httk_workflow_parameter(name, value, fallback, status)) omitsfallbackto pass CNULL; an absent optionalstatusis simply not written. The NUL-terminatedchar *[]tail each verb forwards to the bridge is an optionalcharacter(len=*), dimension(:)argument (args,files,assignments): each element becomes one bridge argument, trailing blanks trimmed, and omitting the argument passes CNULL.
Every integer-returning verb returns the bridge exit status directly as a plain
integer. Every read takes an optional integer, intent(out) :: status that
carries the bridge exit status distinguishing an absent answer from a refused
call (the constants below). call ignore(verb(...)) discards a status a step
body does not inspect, the frequent publish-and-move-on case.
The Fortran function table¶
Each Fortran procedure is one C function, which is one bridge subcommand — the
same subcommand the paired Bash function calls — so this table’s C column is the
Native C runner API row this SDK realizes, and through it the Python and Bash authoring parity
row. args/files/assignments are optional character(len=*) arrays;
status is the optional bridge-status out-argument; fallback is an optional
default. Reads are subroutines (marked sub): the string arrives in the
intent(out), allocatable argument named in the signature
(value/operation/job_key/job/id), which is left unallocated when the
answer is absent. Every other verb is an integer function returning the bridge
status.
Fortran |
C |
|---|---|
|
|
|
|
|
( |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Booleans are Fortran logical: httk_workflow_remove’s missing_ok is an
optional logical, marshalled to the C int flag. As in C, the httk_vasp_*
surface of the Bash SDK has no dedicated wrappers; reach a vasp-* verb through
httk_workflow_invoke, which is why the example below runs the configured
command through httk_workflow_run and classifies its result.
Exit codes¶
The three-status discipline is re-exported as module parameters, identical to the C SDK’s:
Status |
Meaning |
|---|---|
|
the call succeeded |
|
the answer is legitimately absent: an unset state key, a missing parameter without a default, a child that was not observed |
|
the call is refused: bad usage, a protocol violation, a corrupt attempt context — also what is returned when |
Reading a value is therefore an ordinary conditional:
integer :: status
character(len=:), allocatable :: energy
call httk_workflow_state_get("energy", energy, status)
if (status == HTTK_WORKFLOW_OK) then ! equivalently: if (allocated(energy))
! resume from energy
end if
httk_workflow_run returns the classified outcome of the program it ran instead:
0, 22 for a nonzero exit, 124 for a timeout whose process group was
terminated, and 125 when a checker or diagnostic stopped it.
The examples/relax_fortran walkthrough¶
examples/relax_fortran/relax.f90 has the same three-step shape as
examples/relax_c (it is not a line-for-line port), in three bind(c) step
functions built entirely on the procedures above:
preparestages the payload POSCAR into the workdir — reading theposcarparameter (defaultfiles/POSCAR) into an allocatable with a byte-exact stream copy — fails by name (httk_workflow_fail("vasp.input_missing", …)) when it is absent, copies an optional INCAR, notes progress withhttk_workflow_runlog_note, andhttk_workflow_advance("run").runresolves the VASP command withhttk_workflow_setting("vasp.command", …)— falling back to avasp_commandparameter, and failingvasp.command_missingwhen neither is set — word-splits it on whitespace, runs it under supervision withhttk_workflow_run, recordsstate_set("classification", "completed")and advances topublishon success, andhttk_workflow_fail("vasp.failed", …)otherwise.publishstages the finished files into the job’s transactional data withhttk_workflow_putwhen the job has a data directory, andhttk_workflow_succeed.
Two divergences from the C example follow from the SDK, not the workflow: a value
with significant trailing whitespace is trimmed at the C boundary, and a single
whitespace-separated command token wider than the example’s fixed ARG_WIDTH
(4096) makes run fail loudly rather than truncate. Neither affects an ordinary
VASP command.
Build and describe it:
cd examples/relax_fortran
make
./relax --describe
Then drive it exactly like docs/quickstart.md, naming the compiled binary as the
runner and the mock VASP as the command:
httk project init --name relax-fortran
httk workflow workspace init . --name default
httk workflow job new --workflow ./relax --step prepare --file POSCAR=POSCAR --data-mode transactional --tag silicon
httk workflow workspace settings set vasp.command "$PWD/../mock_vasp.py"
httk workflow run
httk workflow collect
The runner reads the poscar parameter (default files/POSCAR), so the POSCAR is
staged with --file POSCAR=POSCAR and the first step is named with --step prepare. The finished calculation lands in jobs/*/data/vasp/, and because every
language SDK publishes through the one bridge, those files are the same bytes the
Python, Bash, and C relaxation runners publish.