diff --git a/docs/src/toml-files.md b/docs/src/toml-files.md index 16616e4147..2802e7e277 100644 --- a/docs/src/toml-files.md +++ b/docs/src/toml-files.md @@ -12,7 +12,6 @@ UUIDs etc. [Code Loading](https://docs.julialang.org/en/v1/manual/code-loading/) in the Julia manual. - ## `Project.toml` The project file describes the project on a high level, for example, the package/project @@ -542,3 +541,10 @@ uuid = "edca9bc6-334e-11e9-3554-9595dbb4349c" There is now an array of the two `B` packages, and the `[deps]` section for `A` has been expanded to be explicit about which `B` package `A` depends on. +## Portable scripts + +Julia scripts can embed project and manifest data inline with fenced +comments such as `#!project … #!project end` and `#!manifest … #!manifest end`. +When the active project is set to a `.jl` file, Pkg reads and writes inline project metadata +from that file. All commands that modify the project or manifest +content (e.g. `Pkg.add`, `Pkg.rm`, `Pkg.update`) will update the inline sections accordingly. diff --git a/ext/REPLExt/REPLExt.jl b/ext/REPLExt/REPLExt.jl index faf7f6cb0c..1e0abd2147 100644 --- a/ext/REPLExt/REPLExt.jl +++ b/ext/REPLExt/REPLExt.jl @@ -67,9 +67,20 @@ function projname(project_file::String) nothing end if project === nothing || project.name === nothing - name = basename(dirname(project_file)) + # For .jl files, use the filename with extension as fallback + # For directories/Project.toml, use the directory name + if endswith(project_file, ".jl") + name = basename(project_file) + else + name = basename(dirname(project_file)) + end else - name = project.name::String + # For portable scripts, include .jl extension to make it clear + if endswith(project_file, ".jl") + name = project.name::String * ".jl" + else + name = project.name::String + end end for depot in Base.DEPOT_PATH envdir = joinpath(depot, "environments") @@ -94,17 +105,25 @@ function promptf() else project_name = projname(project_file) if project_name !== nothing - root = Types.find_root_base_project(project_file) - rootname = projname(root) - if root !== project_file - path_prefix = "/" * dirname(Types.relative_project_path(root, project_file)) + # For portable scripts (.jl files), don't show directory path + if endswith(project_file, ".jl") + if textwidth(project_name) > 30 + project_name = first(project_name, 27) * "..." + end + prefix = "($(project_name)) " else - path_prefix = "" - end - if textwidth(rootname) > 30 - rootname = first(rootname, 27) * "..." + root = Types.find_root_base_project(project_file) + rootname = projname(root) + if root !== project_file + path_prefix = "/" * dirname(Types.relative_project_path(root, project_file)) + else + path_prefix = "" + end + if textwidth(rootname) > 30 + rootname = first(rootname, 27) * "..." + end + prefix = "($(rootname)$(path_prefix)) " end - prefix = "($(rootname)$(path_prefix)) " prev_prefix = prefix prev_project_timestamp = mtime(project_file) prev_project_file = project_file diff --git a/src/API.jl b/src/API.jl index 9acdcf79e3..0494ac19b5 100644 --- a/src/API.jl +++ b/src/API.jl @@ -1405,7 +1405,10 @@ function activate(; temp = false, shared = false, prev = false, io::IO = stderr_ end Base.ACTIVE_PROJECT[] = nothing p = Base.active_project() - p === nothing || printpkgstyle(io, :Activating, "project at $(pathrepr(dirname(p)))") + if p !== nothing + loc = endswith(p, ".jl") ? pathrepr(p) : "$(pathrepr(dirname(p)))" + printpkgstyle(io, :Activating, "project at $loc") + end add_snapshot_to_undo() return nothing end @@ -1465,7 +1468,8 @@ function activate(path::AbstractString; shared::Bool = false, temp::Bool = false p = Base.active_project() if p !== nothing n = ispath(p) ? "" : "new " - printpkgstyle(io, :Activating, "$(n)project at $(pathrepr(dirname(p)))") + loc = endswith(p, ".jl") ? pathrepr(p) : "$(pathrepr(dirname(p)))" + printpkgstyle(io, :Activating, "$(n)project at $loc") end add_snapshot_to_undo() return nothing diff --git a/src/Operations.jl b/src/Operations.jl index 077b6aaacb..a1f5e49dc4 100644 --- a/src/Operations.jl +++ b/src/Operations.jl @@ -1537,8 +1537,8 @@ end ################################ function prune_manifest(env::EnvCache) - # if project uses another manifest, only prune project entry in manifest - if isempty(env.workspace) && dirname(env.project_file) != dirname(env.manifest_file) + # if project uses another manifest (and we are not a portable script), only prune project entry in manifest + if isempty(env.workspace) && dirname(env.project_file) != dirname(env.manifest_file) && !endswith(env.project_file, ".jl") proj_entry = env.manifest[env.project.uuid] proj_entry.deps = env.project.deps else @@ -3477,8 +3477,8 @@ function git_head_env(env, project_dir) git_path = LibGit2.path(repo) project_path = relpath(env.project_file, git_path) manifest_path = relpath(env.manifest_file, git_path) - new_env.project = read_project(GitTools.git_file_stream(repo, "HEAD:$project_path", fakeit = true)) - new_env.manifest = read_manifest(GitTools.git_file_stream(repo, "HEAD:$manifest_path", fakeit = true)) + new_env.project = read_project(GitTools.git_file_stream(repo, "HEAD:$project_path", fakeit = true); source_file = project_path) + new_env.manifest = read_manifest(GitTools.git_file_stream(repo, "HEAD:$manifest_path", fakeit = true); source_file = manifest_path) return new_env end catch err diff --git a/src/Types.jl b/src/Types.jl index ecd1c29ed7..8800620275 100644 --- a/src/Types.jl +++ b/src/Types.jl @@ -55,9 +55,12 @@ end # See loading.jl const TOML_CACHE = Base.TOMLCache(Base.TOML.Parser{Dates}()) const TOML_LOCK = ReentrantLock() + # Some functions mutate the returning Dict so return a copy of the cached value here -parse_toml(toml_file::AbstractString) = - Base.invokelatest(deepcopy_toml, Base.parsed_toml(toml_file, TOML_CACHE, TOML_LOCK))::Dict{String, Any} +function parse_toml(toml_file::AbstractString; manifest::Bool=false, project::Bool=!manifest) + dd = Base.parsed_toml(toml_file, TOML_CACHE, TOML_LOCK; manifest=manifest, project=project) + return Base.invokelatest(deepcopy_toml, dd)::Dict{String, Any} +end ################# # Pkg Error # @@ -68,6 +71,149 @@ end pkgerror(msg::String...) = throw(PkgError(join(msg))) Base.showerror(io::IO, err::PkgError) = print(io, err.msg) +################################# +# Portable script functionality # +################################# +_inline_kind_str(kind::Symbol) = kind === :project ? "project" : "manifest" + +_detect_newline(source::String) = occursin("\r\n", source) ? "\r\n" : "\n" + +function _append_block(base::String, block::String, newline::String) + isempty(block) && return base + if isempty(base) + return block + elseif endswith(base, newline * newline) + return base * block + elseif endswith(base, newline) + return base * newline * block + else + return base * newline * newline * block + end +end + +function _render_inline_block(kind::Symbol, toml::String, newline::String) + kind_str = _inline_kind_str(kind) + lines = split(toml, '\n'; keepempty = true) + if !isempty(lines) && isempty(lines[end]) + pop!(lines) + end + body = [isempty(line) ? "#" : "# " * line for line in lines] + block = [ + "#!" * kind_str * " begin", + body..., + "#!" * kind_str * " end", + ] + return join(block, newline) * newline +end + +function _find_inline_section(source::String, kind::Symbol) + kind_str = _inline_kind_str(kind) + begin_marker = "#!$(kind_str) begin" + end_marker = "#!$(kind_str) end" + + begin_range = findfirst(begin_marker, source) + begin_range === nothing && return nothing + + line_start_idx = findprev(isequal('\n'), source, first(begin_range)) + span_start = line_start_idx === nothing ? firstindex(source) : nextind(source, line_start_idx) + + end_range = findnext(end_marker, source, last(begin_range)) + end_range === nothing && return nothing + + line_end_idx = findnext(isequal('\n'), source, last(end_range)) + span_end = line_end_idx === nothing ? last(end_range) : line_end_idx + + block = source[span_start:span_end] + newline = occursin("\r\n", block) ? "\r\n" : _detect_newline(source) + return (span = span_start:span_end, newline = newline) +end + +function _rewrite_inline_section(source::String, kind::Symbol; toml::Union{Nothing, String} = nothing) + section = _find_inline_section(source, kind) + if toml === nothing + if section === nothing + return source + end + prefix = first(section.span) == firstindex(source) ? "" : source[firstindex(source):prevind(source, first(section.span))] + suffix = last(section.span) >= lastindex(source) ? "" : source[nextind(source, last(section.span)):lastindex(source)] + return prefix * suffix + end + + newline = section === nothing ? _detect_newline(source) : section.newline + replacement = _render_inline_block(kind, toml, newline) + + if section === nothing + if kind === :project + new_source = _append_block("", replacement, newline) + return _append_block(new_source, source, newline) + else + project_section = _find_inline_section(source, :project) + if project_section === nothing + project_block = _render_inline_block(:project, "", newline) + new_source = _append_block("", project_block, newline) + new_source = _append_block(new_source, source, newline) + return _append_block(new_source, replacement, newline) + else + return _append_block(source, replacement, newline) + end + end + end + + prefix = first(section.span) == firstindex(source) ? "" : source[firstindex(source):prevind(source, first(section.span))] + suffix = last(section.span) >= lastindex(source) ? "" : source[nextind(source, last(section.span)):lastindex(source)] + separator = isempty(suffix) || startswith(suffix, newline) ? "" : newline + return prefix * replacement * separator * suffix +end + +function _update_inline_section!(path::AbstractString, kind::Symbol, toml::String) + source = read(path, String) + new_source = _rewrite_inline_section(source, kind; toml = toml) + new_source === source && return nothing + open(path, "w") do io + write(io, new_source) + end + return nothing +end + +function remove_inline_section!(path::AbstractString, kind::Symbol) + source = read(path, String) + new_source = _rewrite_inline_section(source, kind) + new_source === source && return nothing + open(path, "w") do io + write(io, new_source) + end + return nothing +end + +_update_inline_section_in_memory(source::String, kind::Symbol, toml::String) = + _rewrite_inline_section(source, kind; toml = toml) + +function update_inline_project!(path::AbstractString, toml::String) + return _update_inline_section!(path, :project, toml) +end + +function update_inline_manifest!(path::AbstractString, toml::String) + return _update_inline_section!(path, :manifest, toml) +end + +function update_inline_project_and_manifest!(path::AbstractString, project_toml::String, manifest_toml::String) + # Atomically update both project and manifest sections in a .jl file + # This ensures that if either update fails, the file is not modified + source = read(path, String) + + # Apply project update + source = _update_inline_section_in_memory(source, :project, project_toml) + + # Apply manifest update + source = _update_inline_section_in_memory(source, :manifest, manifest_toml) + + # Write once at the end + open(path, "w") do io + write(io, source) + end + return nothing +end + ############### # PackageSpec # ############### @@ -212,7 +358,9 @@ function find_project_file(env::Union{Nothing, String} = nothing) project_file = Base.load_path_expand(env) project_file === nothing && pkgerror("package environment does not exist: $env") elseif env isa String - if isdir(env) + if isfile(env) + project_file = abspath(env) + elseif isdir(env) isempty(readdir(env)) || pkgerror("environment is a package directory: $env") project_file = joinpath(env, Base.project_names[end]) else @@ -220,11 +368,11 @@ function find_project_file(env::Union{Nothing, String} = nothing) abspath(env, Base.project_names[end]) end end - if isfile(project_file) && !contains(basename(project_file), "Project") + if isfile(project_file) && !contains(basename(project_file), "Project") && !endswith(project_file, ".jl") pkgerror( """ The active project has been set to a file that isn't a Project file: $project_file - The project path must be to a Project file or directory. + The project path must be to a Project file or directory or a julia file. """ ) end @@ -445,9 +593,41 @@ function EnvCache(env::Union{Nothing, String} = nothing) end dir = abspath(project_dir) - manifest_file = manifest_file !== nothing ? - (isabspath(manifest_file) ? manifest_file : abspath(dir, manifest_file)) : - manifestfile_path(dir)::String + + # Save the original project before any modifications + original_project = deepcopy(project) + + # For .jl files, handle inline_manifest flag and fix inconsistent states + if endswith(project_file, ".jl") + inline_manifest = get(project.other, "inline_manifest", true)::Bool + + # Case 1: inline_manifest=false but no manifest path + # User wants external manifest but hasn't set it up yet + if !inline_manifest && project.manifest === nothing + script_name = splitext(basename(project_file))[1] + manifest_name = "$(script_name)-Manifest.toml" + manifest_file = abspath(dir, manifest_name) + project.manifest = manifest_name + # Case 2: inline_manifest=true (or default) but has manifest path + # User wants inline manifest but still has external path set + elseif inline_manifest && project.manifest !== nothing + # Load from external path for reading this time + manifest_file = isabspath(project.manifest) ? project.manifest : abspath(dir, project.manifest) + # But clear the path so it gets written inline later + # (We'll clean up the external file in write_env) + # Case 3: inline_manifest=false and has manifest path (consistent state) + elseif !inline_manifest && project.manifest !== nothing + manifest_file = isabspath(project.manifest) ? project.manifest : abspath(dir, project.manifest) + # Case 4: inline_manifest=true and no manifest path (consistent state, default) + else + manifest_file = project_file + end + else + # For regular .toml files, use standard logic + manifest_file = manifest_file !== nothing ? + (isabspath(manifest_file) ? manifest_file : abspath(dir, manifest_file)) : + manifestfile_path(dir)::String + end write_env_usage(manifest_file, "manifest_usage.toml") manifest = read_manifest(manifest_file) @@ -459,7 +639,7 @@ function EnvCache(env::Union{Nothing, String} = nothing) project, workspace, manifest, - deepcopy(project), + original_project, deepcopy(manifest), ) @@ -1413,12 +1593,62 @@ function write_env( pkgerror("Cannot modify a readonly environment. The project at $(env.project_file) is marked as readonly.") end - if (env.project != env.original_project) && (!skip_writing_project) - write_project(env, skip_readonly_check) + # Handle transitions for portable scripts + transitioning_to_inline = false + if endswith(env.project_file, ".jl") + inline_manifest = get(env.project.other, "inline_manifest", true)::Bool + + # If transitioning to inline and we had an external manifest, clean it up + if inline_manifest && env.project.manifest !== nothing + transitioning_to_inline = true + external_manifest_path = isabspath(env.project.manifest) ? env.project.manifest : + abspath(dirname(env.project_file), env.project.manifest) + # Clear the manifest path so it writes inline + env.project.manifest = nothing + # Update manifest_file to point to the script file for inline writing + env.manifest_file = env.project_file + # Clean up external manifest directory + isfile(external_manifest_path) && rm(external_manifest_path; force=true) + end end - if env.manifest != env.original_manifest - write_manifest(env) + + # For .jl files with inline manifest, perform atomic updates + project_needs_update = (env.project != env.original_project) && (!skip_writing_project) + manifest_needs_update = env.manifest != env.original_manifest || transitioning_to_inline + + if endswith(env.project_file, ".jl") + inline_manifest = get(env.project.other, "inline_manifest", true)::Bool + + if inline_manifest + # Both project and manifest are in the same file - update atomically + if project_needs_update || manifest_needs_update + # Prepare both strings even if only one needs updating + project_str = sprint(write_project, destructure(env.project)) + manifest_str = sprint(write_manifest, destructure(env.manifest)) + # Atomically update both sections + update_inline_project_and_manifest!(env.project_file, project_str, manifest_str) + end + else + # External manifest - write separately + if project_needs_update + write_project(env, skip_readonly_check) + end + if manifest_needs_update + write_manifest(env) + end + # Remove the inline manifest section since we're using external + remove_inline_section!(env.project_file, :manifest) + end + else + # Regular TOML files - write separately + if project_needs_update + write_project(env, skip_readonly_check) + end + if manifest_needs_update + write_manifest(env) + end end + return update_undo && Pkg.API.add_snapshot_to_undo(env) end diff --git a/src/manifest.jl b/src/manifest.jl index ccf9cd3942..903dfd8991 100644 --- a/src/manifest.jl +++ b/src/manifest.jl @@ -295,12 +295,31 @@ function Manifest(raw::Dict{String, Any}, f_or_io::Union{String, IO})::Manifest return validate_manifest(julia_version, project_hash, manifest_format, stage1, other, registries) end -function read_manifest(f_or_io::Union{String, IO}) +function read_manifest(f_or_io::Union{String, IO}; source_file::Union{String, Nothing} = nothing) raw = try if f_or_io isa IO - TOML.parse(read(f_or_io, String)) - else - isfile(f_or_io) ? parse_toml(f_or_io) : return Manifest() + # TODO Ugly + # If source_file indicates a .jl file, write to temp file and parse as inline + if source_file !== nothing && endswith(source_file, ".jl") + content = read(f_or_io, String) + temp_file = tempname() * ".jl" + try + write(temp_file, content) + parse_toml(temp_file, manifest=true) + finally + rm(temp_file; force=true) + end + else + TOML.parse(read(f_or_io, String)) + end + elseif f_or_io isa String + if !isfile(f_or_io) + return Manifest() + elseif endswith(f_or_io, ".jl") + parse_toml(f_or_io, manifest=true) + else + parse_toml(f_or_io) + end end catch e if e isa TOML.ParserError @@ -315,7 +334,7 @@ function read_manifest(f_or_io::Union{String, IO}) raw = convert_v1_format_manifest(raw) end end - return Manifest(raw, f_or_io) + return Manifest(raw, source_file !== nothing ? source_file : f_or_io) end function convert_v1_format_manifest(old_raw_manifest::Dict) @@ -476,6 +495,11 @@ function write_manifest(io::IO, raw_manifest::Dict) return nothing end function write_manifest(raw_manifest::Dict, manifest_file::AbstractString) + if endswith(manifest_file, ".jl") + str = sprint(write_manifest, raw_manifest) + update_inline_manifest!(manifest_file, str) + return nothing + end str = sprint(write_manifest, raw_manifest) mkpath(dirname(manifest_file)) return write(manifest_file, str) diff --git a/src/project.jl b/src/project.jl index c4b5f82983..db778ff0e5 100644 --- a/src/project.jl +++ b/src/project.jl @@ -244,12 +244,31 @@ function Project(raw::Dict; file = nothing) return project end -function read_project(f_or_io::Union{String, IO}) +function read_project(f_or_io::Union{String, IO}; source_file::Union{String, Nothing} = nothing) raw = try if f_or_io isa IO - TOML.parse(read(f_or_io, String)) - else - isfile(f_or_io) ? parse_toml(f_or_io) : return Project() + # TODO: This is kind of ugly and seems to only be used for git diff status. + # If source_file indicates a .jl file, write to temp file and parse as inline + if source_file !== nothing && endswith(source_file, ".jl") + content = read(f_or_io, String) + temp_file = tempname() * ".jl" + try + write(temp_file, content) + parse_toml(temp_file, project=true) + finally + rm(temp_file; force=true) + end + else + TOML.parse(read(f_or_io, String)) + end + elseif f_or_io isa String + if !isfile(f_or_io) + return Project() + elseif endswith(f_or_io, ".jl") + parse_toml(f_or_io, project=true) + else + parse_toml(f_or_io) + end end catch e if e isa TOML.ParserError @@ -257,7 +276,7 @@ function read_project(f_or_io::Union{String, IO}) end pkgerror("Errored when reading $f_or_io, got: ", sprint(showerror, e)) end - return Project(raw; file = f_or_io isa IO ? nothing : f_or_io) + return Project(raw; file = f_or_io isa IO ? source_file : f_or_io) end @@ -345,6 +364,11 @@ function write_project(io::IO, project::Dict) return nothing end function write_project(project::Dict, project_file::AbstractString) + if endswith(project_file, ".jl") + str = sprint(write_project, project) + update_inline_project!(project_file, str) + return nothing + end str = sprint(write_project, project) mkpath(dirname(project_file)) return write(project_file, str) diff --git a/test/inline.jl b/test/inline.jl new file mode 100644 index 0000000000..8c65fbf21f --- /dev/null +++ b/test/inline.jl @@ -0,0 +1,71 @@ +using Test +using UUIDs +import Pkg + +const MARKDOWN_UUID = UUID("d6f4376e-aef5-505a-96c1-9c027394607a") + +@testset "Inline project creation and package management" begin + mktempdir() do dir + # Start with an empty .jl file + script_path = joinpath(dir, "inline_app.jl") + write(script_path, "println(\"hello inline\")\n") + + # Activate the script as the project + Pkg.activate(script_path) + + # Initialize the project by adding a package + Pkg.add("Markdown") + + # Verify inline sections were created + source = read(script_path, String) + @test contains(source, "#!project begin") + @test contains(source, "#!manifest begin") + + # Verify project was created correctly + project = Pkg.Types.read_project(script_path) + @test haskey(project.deps, "Markdown") + @test project.deps["Markdown"] == MARKDOWN_UUID + + # Verify manifest was created + manifest = Pkg.Types.read_manifest(script_path) + @test haskey(manifest.deps, MARKDOWN_UUID) + + # Test that the original code is preserved + @test contains(source, "println(\"hello inline\")") + end +end + +@testset "Inline project read/write" begin + mktempdir() do dir + # Start with an empty .jl file + script_path = joinpath(dir, "inline_app2.jl") + write(script_path, "# My script\n") + + # Activate and add package + Pkg.activate(script_path) + Pkg.add("Markdown") + + # Read the project and manifest + project = Pkg.Types.read_project(script_path) + @test haskey(project.deps, "Markdown") + + manifest = Pkg.Types.read_manifest(script_path) + @test haskey(manifest.deps, MARKDOWN_UUID) + entry = manifest[MARKDOWN_UUID] + original_version = entry.version + + # Modify and write back + entry.version = VersionNumber("99.99.99") + Pkg.Types.write_manifest(manifest, script_path) + + # Reload and verify + manifest_reloaded = Pkg.Types.read_manifest(script_path) + @test manifest_reloaded[MARKDOWN_UUID].version == VersionNumber("99.99.99") + + # Verify inline sections still exist + source = read(script_path, String) + @test contains(source, "#!project begin") + @test contains(source, "#!manifest begin") + @test contains(source, "# My script") + end +end diff --git a/test/portable_scripts.jl b/test/portable_scripts.jl new file mode 100644 index 0000000000..7333da9fe3 --- /dev/null +++ b/test/portable_scripts.jl @@ -0,0 +1,90 @@ +using Test +using UUIDs +import Pkg + +const EXAMPLE_UUID = UUID("7876af07-990d-54b4-ab0e-23690620f79a") + +function write_script(path::AbstractString, code::AbstractString) + open(path, "w") do io + write(io, code) + end +end + +@testset "Portable scripts inline manifest lifecycle" begin + mktempdir() do dir + script_path = joinpath(dir, "portable_script.jl") + initial_code = """ + #!/usr/bin/env julia + # portable script + + println("Hello from portable script!") + + function greet(name) + println("Hello, \$name!") + end + + greet("World") + """ + + write_script(script_path, initial_code) + + Pkg.activate(script_path) + Pkg.add("Example") + + content_after_add = read(script_path, String) + @test occursin("#!project begin", content_after_add) + @test occursin("#!manifest begin", content_after_add) + @test occursin("Example", content_after_add) + @test occursin(initial_code, content_after_add) + + Pkg.add("Example") + @test read(script_path, String) == content_after_add + + Pkg.rm("Example") + final_content = read(script_path, String) + + @test occursin(initial_code, final_content) + @test occursin("#!project begin", final_content) + @test occursin("#!manifest begin", final_content) + @test !occursin("Example", final_content) + @test occursin("#!project end\n\n#!manifest begin", final_content) + + project = Pkg.Types.read_project(script_path) + @test !haskey(project.deps, "Example") + + manifest = Pkg.Types.read_manifest(script_path) + @test !haskey(manifest.deps, EXAMPLE_UUID) + + Pkg.activate(; temp = true) + end +end + +@testset "Portable scripts external manifest" begin + mktempdir() do dir + script_path = joinpath(dir, "portable_external.jl") + write_script(script_path, "println(\"portable external\")\n") + + Pkg.activate(script_path) + Pkg.add("Example") + + project = Pkg.Types.read_project(script_path) + project.other["inline_manifest"] = false + Pkg.Types.write_project(project, script_path) + + Pkg.resolve() + + manifest_path = joinpath(dir, "portable_external-Manifest.toml") + @test isfile(manifest_path) + + source = read(script_path, String) + @test occursin("#!project begin", source) + @test !occursin("#!manifest begin", source) + @test occursin("inline_manifest = false", source) + + manifest = Pkg.Types.read_manifest(manifest_path) + @test haskey(manifest.deps, EXAMPLE_UUID) + + Pkg.rm("Example") + Pkg.activate(; temp = true) + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 358ef74b6e..bcc39e8579 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -61,6 +61,8 @@ module PkgTestsInner "force_latest_compatible_version.jl", "manifests.jl", "project_manifest.jl", + "inline.jl", + "portable_scripts.jl", "sources.jl", "workspaces.jl", "apps.jl",