Passing simulation parameters into app

Your code using include already works for me, if I correct the brackets in invokelatest to Base.invokelatest(p.b[1], 3) and remove the string_to_funct line. To create an app, you should of course also move the “inputs.jl” argument into ARGS.

Full program
Folder structure
Tester
│   Manifest.toml
│   precompile_app.jl
│   Project.toml
│
├───compiled
│   ├───bin
│   │       julia.exe
│   │       ...
│   │       Tester.exe
│   │
│   └───share
│       └───julia
│               cert.pem
│               LocalPreferences.toml
│               Project.toml
│
├───inputs
│       inputs.jl
│       inputs2.jl
│
└───src
        Tester.jl

Most of these files are created by Pkg

using Pkg
Pkg.generate("Tester")

and PackageCompiler (see below).

Manually added files are Tester.jl, which corresponds to your main.jl,

Tester.jl
module Tester

# Load parameters from file and call solver
module Tester

# Load parameters from file and call solver
function julia_main()::Cint
    include(ARGS[1])  # Loads in p
	
    # Run solver with input parameters
    solver(p)
    return 0
end

function solver(p)
    println("Running solver with p = ",p)
    println("a = ",p.a)  # 1 for inputs1.jl, 3 for inputs2.jl
    println("p.b[1] = ", p.b[1])
    println("b[1](3) = ", Base.invokelatest(p.b[1], 3)) # 3^2 = 9 for inputs1.jl, 3^3 = 27 for inputs2.jl
end

end

the input files

inputs1.jl
p = (
    a = 1,
    b = [(t) -> t^2]
)

which is your inputs.jl

inputs2.jl
p = (
    a = 3,
    b = [(t) -> t^3]
)

and the optional precompilation script precompile_app.jl, which is not really useful for this small example, but could be for your full program.

precompile_app.jl
using Tester

push!(ARGS, "./inputs/inputs1.jl")
Tester.julia_main()

(cf. PackageCompiler.jl/examples/MyApp/precompile_app.jl at master · JuliaLang/PackageCompiler.jl · GitHub and PackageCompiler.jl how to specify ARGS for create_app? - #2 by jsjie)

To compile, I opened Julia in the parent directory of Tester, and used

]activate Tester
using PackageCompiler
create_app("Tester", "Tester/compiled")  
# or: create_app("Tester", "Tester/compiled", precompile_execution_file="Tester/precompile_app.jl")

(assuming ./Tester/compiled does not already exist). As output I get

> .\Tester\compiled\bin\Tester.exe .\Tester\inputs\inputs1.jl
Running solver with p = (a = 1, b = [Tester.var"#1#2"()])
a = 1
p.b[1] = #1
b[1](3) = 9
> .\Tester\compiled\bin\Tester.exe .\Tester\inputs\inputs2.jl
Running solver with p = (a = 3, b = [Tester.var"#1#2"()])
a = 3
p.b[1] = #1
b[1](3) = 27

You will notice that this is on Windows. If relevant, here’s my full versioninfo():

versioninfo
Julia Version 1.10.4
Commit 48d4fd4843 (2024-06-04 10:41 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: 8 × Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-15.0.7 (ORCJIT, skylake)
Threads: 8 default, 0 interactive, 4 GC (on 8 virtual cores)
Environment:
  JULIA_NUM_THREADS = auto

and I am using PackageCompiler v2.1.17.


I kind of like the include approach you suggest, due to the amount of freedom it brings (you can just ‘import’ any object produced by Julia code), but I cannot say if there are any downsides, or what the idiomatic approach would be.