From b042c0219230ff8772791ad714debb6c3832812c Mon Sep 17 00:00:00 2001 From: Kristoffer Date: Tue, 7 Nov 2023 12:46:28 +0100 Subject: [PATCH 1/3] Introduce -m/--module flag to execute a `main` function in a package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 `.__main__.py`, the equivalent Julia command (`julia -m Package args`) instead runs `Package.main(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 ``` --- NEWS.md | 1 + base/client.jl | 9 ++++++++- doc/man/julia.1 | 5 +++++ src/jloptions.c | 11 ++++++++++- test/loading.jl | 3 +++ test/project/Rot13/Project.toml | 3 +++ test/project/Rot13/src/Rot13.jl | 15 +++++++++++++++ 7 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 test/project/Rot13/Project.toml create mode 100644 test/project/Rot13/src/Rot13.jl diff --git a/NEWS.md b/NEWS.md index 6eb7e6509c69f..73109e12f9c05 100644 --- a/NEWS.md +++ b/NEWS.md @@ -56,6 +56,7 @@ difference between defining a `main` function and executing the code directly at * The `--compiled-modules` and `--pkgimages` flags can now be set to `existing`, which will cause Julia to consider loading existing cache files, but not to create new ones ([#50586] and [#52573]). +* The `-m/--module` flag can be passed to run the `@main` function inside a package with a set of arguments. Multi-threading changes ----------------------- diff --git a/base/client.jl b/base/client.jl index 201792c786b51..6cbc1b4c07c54 100644 --- a/base/client.jl +++ b/base/client.jl @@ -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 @@ -292,6 +292,13 @@ function exec_options(opts) elseif cmd == 'E' invokelatest(show, Core.eval(Main, parse_input_line(arg))) println() + elseif cmd == 'm' + @eval Main import $(Symbol(arg)).main + if !should_use_main_entrypoint() + error("`main` in `$arg` not declared as entry point (use `@main` to do so)") + end + return false + elseif cmd == 'L' # load file immediately on all processors if !distributed_mode diff --git a/doc/man/julia.1 b/doc/man/julia.1 index ee0f973c259fd..e04bb23c05488 100644 --- a/doc/man/julia.1 +++ b/doc/man/julia.1 @@ -104,6 +104,11 @@ Enable or disable usage of native code caching in the form of pkgimages -e, --eval Evaluate +.TP +-m, --module [args] +Run entry point of `Package` (`@main` function) with `args'. + + .TP -E, --print Evaluate and display the result diff --git a/src/jloptions.c b/src/jloptions.c index 5d627687a38be..ed632071eed53 100644 --- a/src/jloptions.c +++ b/src/jloptions.c @@ -118,6 +118,8 @@ static const char opts[] = // actions " -e, --eval Evaluate \n" " -E, --print Evaluate and display the result\n" + " -m, --module [args]\n" + " Run entry point of `Package` (`@main` function) with `args'.\n" " -L, --load Load immediately on all processors\n\n" // parallel options @@ -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 @@ -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 }, @@ -411,6 +414,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; @@ -424,6 +428,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 @@ -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: jl_options.code_coverage = codecov; jl_options.malloc_log = malloclog; int proc_args = *argcp < optind ? *argcp : optind; diff --git a/test/loading.jl b/test/loading.jl index 5d43cd085f5d6..16f4dc4344ccf 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -1503,4 +1503,7 @@ end @test occursin(r"Generating object cache file for Parent", log) @test occursin(r"Loading object cache file .+ for Parent", log) 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 abjurer NOWHERE " end diff --git a/test/project/Rot13/Project.toml b/test/project/Rot13/Project.toml new file mode 100644 index 0000000000000..eb03cb84d588e --- /dev/null +++ b/test/project/Rot13/Project.toml @@ -0,0 +1,3 @@ +name = "Rot13" +uuid = "43ef800a-eac4-47f4-949b-25107b932e8f" +version = "0.1.0" diff --git a/test/project/Rot13/src/Rot13.jl b/test/project/Rot13/src/Rot13.jl new file mode 100644 index 0000000000000..0672799d61f24 --- /dev/null +++ b/test/project/Rot13/src/Rot13.jl @@ -0,0 +1,15 @@ +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) + foreach(arg -> print(rot13(arg), " "), ARGS) + return 0 +end + +end # module Rot13 From b4ecf71b31fdd3b07fbe71c990d38fd403e041f9 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Fri, 15 Dec 2023 15:20:23 +0100 Subject: [PATCH 2/3] small NEWS update --- NEWS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 73109e12f9c05..dc9705c15774a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -56,7 +56,8 @@ difference between defining a `main` function and executing the code directly at * The `--compiled-modules` and `--pkgimages` flags can now be set to `existing`, which will cause Julia to consider loading existing cache files, but not to create new ones ([#50586] and [#52573]). -* The `-m/--module` flag can be passed to run the `@main` function inside a package with a set of arguments. +* The `-m/--module` flag can be passed to run the `main` function inside a package with a set of arguments. + This `main` function should be declared using `@main` to indicate that it is an entry point. Multi-threading changes ----------------------- From dd885e96abbf49e1b6381cb66c7e0de063f1bbc7 Mon Sep 17 00:00:00 2001 From: Kristoffer Date: Thu, 1 Feb 2024 14:18:12 +0100 Subject: [PATCH 3/3] fix rebase error --- test/loading.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/loading.jl b/test/loading.jl index 16f4dc4344ccf..de103bb4963e2 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -1503,6 +1503,8 @@ end @test occursin(r"Generating object cache file for Parent", log) @test occursin(r"Loading object cache file .+ for Parent", log) 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 abjurer NOWHERE "