-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Code loading: add support for "portable scripts". #59982
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
f4374bd to
ae11fab
Compare
|
What will happen here if there is only a |
|
Yeah, that's what the
question is about. In order to run the script you at least need to do a resolve it to have manifest data available so you have some concrete versions to load. Maybe julia should do that automatically if it tries to run a portable script without manifest information. But then where should the result be stored? inline or in some hashed directory somewhere? Also, when running a portable script julia should maybe disable the global env from the load path by default? |
|
Oops sorry, I missed you listing that. Yeah, what I was picturing was that we could would have a folder for script environments in Maybe there's a better way to handle that. |
Only manifest files, right? If you don't even have an inline project section, then you are just a normal julia file, or? |
|
I was imagining that project info from the script would get copied there, and if there is a manifest it also gets copied. But maybe that's not necessary and you don't need copies, just writing out a manifest if it's not embedded. |
We just read it inline now, so no need for that really. What should the |
|
In the silly little implementation I played with recently, I was just replacing the path separators in the absolute path with underscores. But there's probably a bunch of reasons why that's a bad idea. |
2af2ff2 to
979f6ce
Compare
|
Could the key for Also it is worth to add that if manifest were to added in the script itself that would make editing such scripts manually by hand much less pleasant. It doesn’t seem like users would want such a level of reproducibility in practice where compat bounds would suffice. |
I thought about that but then you get collisions if you have different scripts that happen to get the same project content.
I disagree, I think you would want to send someone a file and have them be able to reproduce stuff? |
|
Wouldn't interpreting the compat bounds as exact versions with which to resolve produce the same |
|
I don't really want to have a discussion here on how the resolver works. The feature can be made to support both inline and outline manifest info, so not sure what there is to discuss more about this. JuliaLang/Pkg.jl#4479 is a slightly WIP follow-up to this from the pkg side. With that, you can do: and the file updates during pkg operations. |
|
One preference from me: I think it would be nice to put/read the manifest from the bottom, so if you open the script and start looking at it you don't see a few hundred lines of TOML up front. |
|
Yeah, I also got to that conclusion. But the project data should still be on top? |
|
Regarding storing the manifest inline or not, I think a key in the project section could determine that. |
On one hand, splitting it does seem a bit odd, but having project info at the top does feel right to me somehow.
I'm guessing there may be some scripts which are a bit "package-like" in that they have compat bounds on packages, and it's fine for those to be resolved when run. In this way, having the manifest section be optional seems nice. |
|
Actually for the outline manifest we can use the |
979f6ce to
9c3d598
Compare
|
Ok, so I have made the following changes (mostly in Pkg).
A UUID feels a bit excessive in the path... |
How about julia> let f="path/to/myscript.jl"; first(splitext(basename(f))) * '_' * string(hash(f), base=62) end
"myscript_Hkm5zPmRBLp"A 64-bit discriminator seems plenty to me, with ~10k scripts there's a ~1 in a trillion chance of a hash collision. Edit: I remembered case-insensitive filesystems exist. Make that |
|
Question: What do we expect It may not make a lot of sense, but I'm sure that we'll see people try it sooner or later. My instinctive feeling is that doing anything other than using the project/manifest embedded in |
|
Yes (and it should also be what it does now). Should add a test :) |
|
I dont think hashing only by path is good because you get a collision if you then move the script and create a new one in the same place with the same name. Since the path to the manifest is stored in the script itself it doesn't really need to have anything to do with the path (which is why I just grabbed for a uuid). |
|
Hey! This looks awesome, very cool to see this getting standardized! Later in the process, I would like to make sure that this can be made compatible with Pluto's format, so that you can activate and run Pluto notebooks in standalone Julia :) @KristofferC asked me to share some experiences from Pluto (which already has a similar feature by default for notebook files), so here you go: General reactionAmong Pluto's users (education, Julia end-users), people really appreciate this feature and it makes reproducibility much more accessible. It makes it much easier to share your work reliably with new Julia users, which is a huge win. Pluto enables this by default (when creating a notebook, and when opening a notebook). For this PR I would also suggest enabling it by default when opening a file with embedded project data, or at least showing a hint. SurprisesEmbedding the Project+Manifest also turned out less amazing than I thought. The main unexpected issues are: Julia versionsIn practice, the manifest is only useful with the same Julia version, and it is pretty common to change Julia versions. (E.g. when sharing work with someone else, or when opening old work.) We made https://github.com/JuliaPluto/GracefulPkg.jl to still get some value out of unresolvable environments in Pluto, but for base Julia you might want to address this. I would be curious to hear your ideas for this issue, and maybe we can share a solution. Setup timesPackage setup times (install+precomp) are long in Julia, and they get longer with every release. Cache reuse is minimal across environments in practice, which means that each new script launch will probably trigger its own lengthy setup process. Recent Julia releases seem more optimised for the "big global env", rather than many specific environments. This is very relevant in Pluto, where you often share your work, sometimes many notebooks. But if you use this feature less frequently, or only for personal work, then cache hit rates will be high. |
I agree with this. The "competition" has it easier in this case because they often don't use the language itself to launch a script (which immediately ties you to that version of the language), but instead have a launcher one abstraction level above, which can also decide what julia version to use. E.g., in We could have something like
Even for Pkg Apps I feel the pain of the non-flexibility of having the launcher of the app not being able to choose julia version. (cc @davidanthoff) |
FWIW, I implemented this feature in Juliaup ~1y ago (in the |
|
The actual Not something that we can do immediately, but it is feasible. |
How about a string macro? ? Could also have a |
|
I'm now thinking that most of the decisions about portable scripts you want to take before executing the file and also if the manifest section is towards the end I'm not sure you will have macro expanded that before the code runs. And you also want the detection to be easy for other tools (like juliaup) so I don't think you get away from raw string processing to parse the inline content. And at that point, maybe magic comments are OK... |
|
I removed the multiline suport and added requirements of project first and manifest last. But note @Keno, there is no "activation error" because we do not look at the TOML files until we start loading packages so you won't get any error until you actually try do a package operation. We could of course try to eagerly detect this but it wouldn't really be consistent with running files in a normal environment. |
… be first, manifest last
72e4af2 to
a21aabf
Compare
This is what Pluto does and it works very well 👍 |
|
One additional comment I have: if we're giving scripts their own project/manifests, it'd be really nice if we could also have the option to give these scripts their own precompilation images the way that packages get their own images. When the pkgimages were new, I tested using one for some Benchmark Games scripts and found it'd result in ~20% wall time speedup in one example. It'd be great if one could use the pkgimage machinery without having to go through all the hassle of making a local package. I'm not asking for this feature to be added in this PR, but I just want to ask if there are design decisions that should be made here that might make implementing "scriptimages" easier. |
|
I don't think it should be too hard. I could prototype something. But I'm not sure if most people want to take on that initial latency. There is also some overlap with Pkg apps as well but maybe that is OK. |
|
I do wonder if some sort of LRU could be helpful for pkg-related RC (and Juliaup Julia versions, but that's it's own conversation). |
|
The discussion about precompiling this spawned a few ideas of an alternative way to implement this and I don't think targeting 1.13 is reasonable when the design space is quite a lot bigger than I originally thought. |
It's really just a question of if it's the sort of script one expects to run ~once per machine, or many times per machine. Both are common, but the fact that it's so painful to work around julia's slow TTFX turns a lot of people off of writing "many times per machine" scripts in julia unless those scripts do a lot of work.
Now that you mention it, support for Pkg apps that are contained in a single-file would be really awesome 👀, but unless there's a way to make the app local to a certain directory, I don't think it can really replace scripts? That said, if we made single-file Apps, and made an option for them to be directory local, they could potentially be the preferred way to do scripting workflows. |
|
W.r.t compiled scripts, I don't see a way in which the script gets precompiled would still allow |
|
I was picturing that if there was a |
|
I punted on precompiled portable scripts for the moment, I'll come back to it. I don't see a nice way of doing it right now without the script author wrapping the script in What I changed in the last commit was that the loading for portable scripts follow the same rules as a package. I think that is correct. Even if it is inconsistent with |
|
After testing this locally, I must say that having the ability to put all the toml info at the end of the file would be quite nice. So how about if we have a comment in the beginning that is the definite marker for portability and then the sections can be wherever. That means we still have a concrete marker at the beginning. |
|
I'm still not a fan of wherever. Either put them at the beginning or at the end, but interleaving metadata and code seems terrible. |
|
Yeah sure we can do only first or last, just that the user decides which of them? |
|
Yes fine by me (with the portable script marker) |
|
I think "portable" is a very overloaded term to use for this; it's fine to call it that informally but less clear if part of the actual syntax. Maybe |
|
UV has a We could have ? |
|
I'mma just do at the top for now. It is so short you can just enable it manually which is nice. |
The first name that came to my mind was "fat script" 😆 |
|
Maybe we can incorporate it into the shbang line? E.g. |
|
But Windows though? |
These are jl files that have project and manifest embedded inside them, similar to how Pluto notebooks have done for quite a while.
The delimiters are
#!manifest beginand#!manifest end(analogous for the project data), and the content can either have single-line comments or be inside a multi-line comment.When the active project is set to a portable script, the project and manifest data will be read from the inlined toml data.
Starting julia with a file (
julia file.jl) will set the file as the active project if julia detects that it is a portable script.The tests and the parser for the inline toml data were written by Claude 🤖 .
I still have to finish the Pkg part (edit JuliaLang/Pkg.jl#4479) that writes these files, so this cannot be tested "end-to-end" right now. However, if one creates a portable script manually like this (rename to .jl)
portable_script.txt, we can check that it works via:
where we can see that the manifest information inside the portable script is used.
Invitation to bike shedding:
Pkg.resolve()? Put it inline or in some temp environment?.jl) to have semantic meaning for the code loading?In addition, I think we need a
--instantiateflag or something in Julia that checks if all the packages are available for the scripts and if not, side-loads Pkg to download them. Maybe that should even be automatic for portable scripts. I think this feature has been requested on the Pkg repo. Being able to get a file and run it in "one shot" is quite tempting to support.Previous work:
https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies
https://internals.rust-lang.org/t/pre-rfc-cargo-script-for-everyone/18639