Skip to content

Commit

Permalink
Introduce -m/--module flag to execute a main function in a package
Browse files Browse the repository at this point in the history
This aims to bring similar functionality to Julia as the `-m` flag for Python which exists to directly run some function in a package and being able to pass arguments to that function.

While in Python, `python -m package args` runs the file `<package>.__main__.py`, the equivalent Julia command (`julia -m Package args`) instead runs `Package.main(args)`. A package can also have a `main` function in a submodule runnable with `julia -m Package.SubModule args`. The package is assumed to be installed in the environment `julia` is run in.

An example usage could be:

Add the package:
```
(@v1.11) pkg> add https://github.com/KristofferC/Rot13.jl
     Cloning git-repo `https://github.com/KristofferC/Rot13.jl`
    Updating git-repo `https://github.com/KristofferC/Rot13.jl`
   Resolving package versions...
    Updating `~/.julia/environments/v1.11/Project.toml`
  [43ef800a] + Rot13 v0.1.0 `https://github.com/KristofferC/Rot13.jl#master`
    Updating `~/.julia/environments/v1.11/Manifest.toml`
  [43ef800a] + Rot13 v0.1.0 `https://github.com/KristofferC/Rot13.jl#master`
```

And then it can be run (since it has a `main` function) via:

```
❯ ./julia/julia -m Rot13 "encrypt this for me" "and this as well"
rapelcg guvf sbe zr
naq guvf nf jryy
```
  • Loading branch information
KristofferC committed Nov 9, 2023
1 parent 529e4e7 commit baa7dd2
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 2 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Command-line option changes
This is intended to unify script and compilation workflows, where code loading may happen
in the compiler and execution of `Main.main` may happen in the resulting executable. For interactive use, there is no semantic
difference between defining a `main` function and executing the code directly at the end of the script ([50974]).
* The `-m/--module` flag can be passed to run the `main` function inside a package with a set of arguments.

Multi-threading changes
-----------------------
Expand Down
26 changes: 25 additions & 1 deletion base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ function exec_options(opts)
if cmd_suppresses_program(cmd)
arg_is_program = false
repl = false
elseif cmd == 'L'
elseif cmd == 'L' || cmd == 'm'
# nothing
elseif cmd == 'B' # --bug-report
# If we're doing a bug report, don't load anything else. We will
Expand Down Expand Up @@ -292,6 +292,30 @@ function exec_options(opts)
elseif cmd == 'E'
invokelatest(show, Core.eval(Main, parse_input_line(arg)))
println()
elseif cmd == 'm'
mods = split(arg, ".")
pkg = popfirst!(mods)
# Some more validation of `mods` and `pkg`?
m = Base.require(Main, Symbol(pkg))
while !isempty(mods)
if !isdefined(m, Symbol(mods[1])) &&
error("`$(mods[1])` not defined")
end
m = getfield(m, Symbol(popfirst!(mods)))
if !(m isa Module)
error("`$(mods[1])` is not a module")
end
end
if !isdefined(m, :main)
error("no `main` method defined in module")
end
main = getfield(m, Symbol("main"))
retval = @invokelatest main(ARGS)
retval === nothing && (retval = 0)
if !(retval isa Integer)
error("`main` method did not return an integer")
end
exit(retval)
elseif cmd == 'L'
# load file immediately on all processors
if !distributed_mode
Expand Down
4 changes: 4 additions & 0 deletions doc/man/julia.1
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ Enable or disable incremental precompilation of modules
-e, --eval <expr>
Evaluate <expr>

.TP
-m, --module <Package>[.SubModule] [args]
Run function <Package>.[SubModule.]main(args)

.TP
-E, --print <expr>
Evaluate <expr> and display the result
Expand Down
11 changes: 10 additions & 1 deletion src/jloptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ static const char opts[] =
// actions
" -e, --eval <expr> Evaluate <expr>\n"
" -E, --print <expr> Evaluate <expr> and display the result\n"
" -m, --module <Package>[.SubModule] [args]\n"
" Run function `<Package>.[SubModule.]main(args))\n"
" -L, --load <file> Load <file> immediately on all processors\n\n"

// parallel options
Expand Down Expand Up @@ -261,7 +263,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
opt_gc_threads,
opt_permalloc_pkgimg
};
static const char* const shortopts = "+vhqH:e:E:L:J:C:it:p:O:g:";
static const char* const shortopts = "+vhqH:e:E:L:J:C:it:p:O:g:m:";
static const struct option longopts[] = {
// exposed command line options
// NOTE: This set of required arguments need to be kept in sync
Expand All @@ -274,6 +276,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
{ "banner", required_argument, 0, opt_banner },
{ "home", required_argument, 0, 'H' },
{ "eval", required_argument, 0, 'e' },
{ "module", required_argument, 0, 'm' },
{ "print", required_argument, 0, 'E' },
{ "load", required_argument, 0, 'L' },
{ "bug-report", required_argument, 0, opt_bug_report },
Expand Down Expand Up @@ -412,6 +415,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
case 'e': // eval
case 'E': // print
case 'L': // load
case 'm': // module
case opt_bug_report: // bug
{
size_t sz = strlen(optarg) + 1;
Expand All @@ -425,6 +429,10 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
ncmds++;
cmds[ncmds] = 0;
jl_options.cmds = cmds;
if (c == 'm') {
optind -= 1;
goto parsing_args_done;
}
break;
}
case 'J': // sysimage
Expand Down Expand Up @@ -860,6 +868,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
"This is a bug, please report it.", c);
}
}
parsing_args_done:
if (codecov || malloclog) {
if (pkgimage_explicit && jl_options.use_pkgimages) {
jl_errorf("julia: Can't use --pkgimages=yes together "
Expand Down
5 changes: 5 additions & 0 deletions test/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1331,3 +1331,8 @@ end
@test filesize(cache_path) != cache_size
end
end

@testset "-m" begin
rot13proj = joinpath(@__DIR__, "project", "Rot13")
@test readchomp(`$(Base.julia_cmd()) --startup-file=no --project=$rot13proj -m Rot13 --project nowhere ABJURER`) == "--cebwrpg\nabjurer\nNOWHERE"
end
3 changes: 3 additions & 0 deletions test/project/Rot13/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name = "Rot13"
uuid = "43ef800a-eac4-47f4-949b-25107b932e8f"
version = "0.1.0"
17 changes: 17 additions & 0 deletions test/project/Rot13/src/Rot13.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Rot13

function rot13(c::Char)
shft = islowercase(c) ? 'a' : 'A'
isletter(c) ? c = shft + (c - shft + 13) % 26 : c
end

rot13(str::AbstractString) = map(rot13, str)

function main(ARGS)
for arg in ARGS
println(rot13(arg))
end
return 0
end

end # module Rot13

0 comments on commit baa7dd2

Please sign in to comment.