julia-vscode / SymbolServer.jl
1 50
module SymbolServer
2

3
export SymbolServerInstance, getstore
4

5
using Pkg, SHA
6
using Base: UUID, Process
7
import Sockets, UUIDs
8

9
include("faketypes.jl")
10
include("symbols.jl")
11
include("utils.jl")
12
include("serialize.jl")
13
using .CacheStore
14

15
mutable struct SymbolServerInstance
16
    process::Union{Nothing,Base.Process}
17
    depot_path::String
18
    canceled_processes::Set{Process}
19
    store_path::String
20

21
    function SymbolServerInstance(depot_path::String="", store_path::Union{String,Nothing}=nothing)
22 70
        return new(nothing, depot_path, Set{Process}(), store_path === nothing ? abspath(joinpath(@__DIR__, "..", "store")) : store_path)
23
    end
24
end
25

26 20
function getstore(ssi::SymbolServerInstance, environment_path::AbstractString, progress_callback=nothing, error_handler=nothing; download = false)
27 70
    !ispath(environment_path) && return :success, recursive_copy(stdlibs)
28

29
    # see if we can download any package cache's before
30 70
    if download
31 0
        manifest_filename = isfile(joinpath(environment_path, "JuliaManifest.toml")) ? joinpath(environment_path, "JuliaManifest.toml") : joinpath(environment_path, "Manifest.toml")
32 0
        if isfile(manifest_filename)
33 0
            let manifest = read_manifest(manifest_filename)
34 0
                if manifest !== nothing
35 0
                    @debug "Downloading cache files for manifest at $(manifest_filename)."
36 0
                    asyncmap(collect(validate_disc_store(ssi.store_path, manifest)), ntasks = 10) do pkg
37
                        uuid = packageuuid(pkg)
38
                        get_file_from_cloud(manifest, uuid, environment_path, ssi.depot_path, ssi.store_path, ssi.store_path)
39
                    end
40
                end
41
            end
42
        end
43
    end
44

45

46 70
    jl_cmd = joinpath(Sys.BINDIR, Base.julia_exename())
47 70
    server_script = joinpath(@__DIR__, "server.jl")
48

49 70
    env_to_use = copy(ENV)
50 70
    env_to_use["JULIA_REVISE"] = "manual" # Try to make sure Revise isn't enabled.
51

52 70
    if ssi.depot_path == ""
53 70
        delete!(env_to_use, "JULIA_DEPOT_PATH")
54
    else
55 0
        env_to_use["JULIA_DEPOT_PATH"] = ssi.depot_path
56
    end
57

58 70
    stderr_for_client_process = VERSION < v"1.1.0" ? nothing : IOBuffer()
59

60 70
    if ssi.process !== nothing
61 60
        to_cancel_p = ssi.process
62 70
        ssi.process = nothing
63 70
        push!(ssi.canceled_processes, to_cancel_p)
64 70
        kill(to_cancel_p)
65
    end
66

67 60
    use_code_coverage = Base.JLOptions().code_coverage
68

69 70
    currently_loading_a_package = false
70 70
    current_package_name = ""
71

72 66
    pipename = Sys.iswindows() ? "\\\\.\\pipe\\vscjlsymserv-$(UUIDs.uuid4())" : joinpath(tempdir(), "vscjlsymserv-$(UUIDs.uuid4())")
73

74 60
    server_is_ready = Channel(1)
75

76 60
    @async try
77 70
        server = Sockets.listen(pipename)
78

79 70
        put!(server_is_ready, nothing)
80

81 70
        conn = Sockets.accept(server)
82

83 70
        s = readline(conn)
84

85 70
        while s != "" && isopen(conn)
86 60
            parts = split(s, ';')
87 60
            if parts[1] == "STARTLOAD"
88 60
                currently_loading_a_package = true
89 60
                current_package_name = parts[2]
90 60
                current_package_uuid = parts[3]
91 60
                current_package_version = parts[4]
92 60
                progress_callback !== nothing && progress_callback(current_package_name)
93 60
            elseif parts[1] == "STOPLOAD"
94 60
                currently_loading_a_package = false
95 60
            elseif parts[1] == "PROCESSPKG"
96 60
                progress_callback !== nothing && progress_callback(parts[2])
97
            else
98 0
                error("Unknown command.")
99
            end
100 60
            s = readline(conn)
101
        end
102
    catch err
103 0
        bt = catch_backtrace()
104 0
        if error_handler !== nothing
105 0
            error_handler(err, bt)
106
        else
107 0
            Base.display_error(stderr, err, bt)
108
        end
109
    end
110

111 60
    take!(server_is_ready)
112

113 70
    p = open(pipeline(Cmd(`$jl_cmd --code-coverage=$(use_code_coverage==0 ? "none" : "user") --startup-file=no --compiled-modules=no --history-file=no --project=$environment_path $server_script $(ssi.store_path) $pipename`, env=env_to_use), stderr=stderr_for_client_process), read=true, write=true)
114 60
    ssi.process = p
115

116 70
    if success(p)
117
        # Now we create a new symbol store and load everything into that
118
        # from disc
119 70
        new_store = recursive_copy(stdlibs)
120 70
        load_project_packages_into_store!(ssi, environment_path, new_store)
121

122 70
        return :success, new_store
123 69
    elseif p in ssi.canceled_processes
124 59
        delete!(ssi.canceled_processes, p)
125

126 69
        return :canceled, nothing
127
    else
128 0
        if currently_loading_a_package
129 0
            return :package_load_crash, (package_name = current_package_name, stderr = stderr_for_client_process)
130
        else
131 0
            return :failure, stderr_for_client_process
132
        end
133
    end
134
end
135

136 20
function load_project_packages_into_store!(ssi::SymbolServerInstance, environment_path, store)
137 70
    project_filename = isfile(joinpath(environment_path, "JuliaProject.toml")) ? joinpath(environment_path, "JuliaProject.toml") : joinpath(environment_path, "Project.toml")
138 70
    project = try
139 70
        Pkg.API.read_project(project_filename)
140
    catch err
141 0
        if err isa Pkg.Types.PkgError
142 0
            @warn "Could not load project."
143 0
            return
144
        else
145 0
            rethrow(err)
146
        end
147
    end
148

149 70
    manifest_filename = isfile(joinpath(environment_path, "JuliaManifest.toml")) ? joinpath(environment_path, "JuliaManifest.toml") : joinpath(environment_path, "Manifest.toml")
150 70
    manifest = read_manifest(manifest_filename)
151 70
    manifest === nothing && return
152

153 70
    for uuid in values(deps(project))
154 70
        load_package_from_cache_into_store!(ssi, uuid, manifest, store)
155
    end
156
end
157

158
"""
159
    load_package_from_cache_into_store!(ssp::SymbolServerInstance, uuid, store)
160

161
Tries to load the on-disc stored cache for a package (uuid). Attempts to generate (and save to disc) a new cache if the file does not exist or is unopenable.
162
"""
163 20
function load_package_from_cache_into_store!(ssi::SymbolServerInstance, uuid, manifest, store)
164 70
    isinmanifest(manifest, uuid) || return
165 70
    pe = frommanifest(manifest, uuid)
166 70
    pe_name = packagename(manifest, uuid)
167 70
    haskey(store, Symbol(pe_name)) && return
168

169
    # further existence checks needed?
170 70
    cache_path = joinpath(ssi.store_path, get_cache_path(manifest, uuid)...)
171 70
    if isfile(cache_path)
172 70
        try
173 60
            package_data = open(cache_path) do io
174 40
                CacheStore.read(io)
175
            end
176 70
            store[Symbol(pe_name)] = package_data.val
177 70
            for dep in deps(pe)
178 70
                load_package_from_cache_into_store!(ssi, packageuuid(dep), manifest, store)
179
            end
180
        catch err
181 0
            Base.display_error(stderr, err, catch_backtrace())
182 0
            @warn "Tried to load $pe_name but failed to load from disc, re-caching."
183 0
            try
184 0
                rm(cache_path)
185
            catch err2
186
                # There could have been a race condition that the file has been deleted in the meantime,
187
                # we don't want to crash then.
188 0
                err2 isa Base.IOError || rethrow(err2)
189
            end
190
        end
191
    else
192 60
        @warn "$(pe_name) not stored on disc"
193 70
        store[Symbol(pe_name)] = ModuleStore(VarRef(nothing, Symbol(pe_name)), Dict{Symbol,Any}(), "$pe_name failed to load.", true, Symbol[], Symbol[])
194
    end
195
end
196

197 20
function clear_disc_store(ssi::SymbolServerInstance)
198 70
    for f in readdir(ssi.store_path)
199 70
        if occursin(f, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
200 70
            rm(joinpath(ssi.store_path, f), recursive = true)
201
        end
202
    end
203
end
204

205
const stdlibs = load_core()
206

207 10
function _precompile_()
208 20
    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing
209 20
    Base.precompile(Tuple{Type{SymbolServer.DataTypeStore},SymbolServer.FakeTypeName,SymbolServer.FakeTypeName,Array{Any,1},Array{Any,1},Array{Symbol,1},Array{Any,1},String,Bool})
210 20
    Base.precompile(Tuple{typeof(SymbolServer.cache_methods),Any,Dict{Symbol,SymbolServer.ModuleStore}})
211 20
    Base.precompile(Tuple{typeof(SymbolServer.getenvtree)})
212 20
    Base.precompile(Tuple{typeof(SymbolServer.symbols),Dict{Symbol,SymbolServer.ModuleStore}})
213 20
    Base.precompile(Tuple{typeof(copy),Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,typeof(SymbolServer._parameter),Tuple{NTuple{4,Symbol}}}})
214
end
215
VERSION >= v"1.4.2" && _precompile_()
216

217
end # module

Read our documentation on viewing source code .

Loading