julia-vscode / SymbolServer.jl
1 25
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 35
        return new(nothing, depot_path, Set{Process}(), store_path === nothing ? abspath(joinpath(@__DIR__, "..", "store")) : store_path)
23
    end
24
end
25

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

29
    # see if we can download any package cache's before 
30 35
    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 = Pkg.Types.read_manifest(manifest_filename)
34 0
                asyncmap(collect(validate_disc_store(ssi.store_path, manifest)), ntasks = 10) do pkg
35
                    uuid = packageuuid(pkg)
36
                    get_file_from_cloud(manifest, uuid, environment_path, ssi.depot_path, ssi.store_path, ssi.store_path)
37
                end
38
            end
39
        end
40
    end
41

42
    
43 35
    jl_cmd = joinpath(Sys.BINDIR, Base.julia_exename())
44 35
    server_script = joinpath(@__DIR__, "server.jl")
45

46 35
    env_to_use = copy(ENV)
47 35
    env_to_use["JULIA_REVISE"] = "manual" # Try to make sure Revise isn't enabled.
48

49 35
    if ssi.depot_path == ""
50 35
        delete!(env_to_use, "JULIA_DEPOT_PATH")
51
    else
52 0
        env_to_use["JULIA_DEPOT_PATH"] = ssi.depot_path
53
    end
54

55 35
    stderr_for_client_process = VERSION < v"1.1.0" ? nothing : IOBuffer()
56

57 35
    if ssi.process !== nothing
58 30
        to_cancel_p = ssi.process
59 35
        ssi.process = nothing
60 35
        push!(ssi.canceled_processes, to_cancel_p)
61 35
        kill(to_cancel_p)
62
    end
63

64 30
    use_code_coverage = Base.JLOptions().code_coverage
65

66 35
    currently_loading_a_package = false
67 35
    current_package_name = ""
68

69 33
    pipename = Sys.iswindows() ? "\\\\.\\pipe\\vscjlsymserv-$(UUIDs.uuid4())" : joinpath(tempdir(), "vscjlsymserv-$(UUIDs.uuid4())")
70

71 30
    server_is_ready = Channel(1)
72

73 30
    @async try
74 35
        server = Sockets.listen(pipename)
75

76 35
        put!(server_is_ready, nothing)
77

78 35
        conn = Sockets.accept(server)
79

80 35
        s = readline(conn)
81

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

108 30
    take!(server_is_ready)
109

110 35
    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)
111 30
    ssi.process = p
112

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

119 35
        return :success, new_store
120 34
    elseif p in ssi.canceled_processes
121 29
        delete!(ssi.canceled_processes, p)
122

123 34
        return :canceled, nothing
124
    else
125 0
        if currently_loading_a_package
126 0
            return :package_load_crash, (package_name = current_package_name, stderr = stderr_for_client_process)
127
        else
128 0
            return :failure, stderr_for_client_process
129
        end
130
    end
131
end
132

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

146 35
    manifest_filename = isfile(joinpath(environment_path, "JuliaManifest.toml")) ? joinpath(environment_path, "JuliaManifest.toml") : joinpath(environment_path, "Manifest.toml")
147 35
    manifest = try
148 35
        Pkg.API.read_manifest(manifest_filename)
149
    catch err
150 0
        if err isa Pkg.Types.PkgError
151 0
            @warn "Could not load manifest."
152 0
            return
153
        else
154 0
            rethrow(err)
155
        end
156
    end
157

158 35
    for uuid in values(deps(project))
159 35
        load_package_from_cache_into_store!(ssi, uuid, manifest, store)
160
    end
161
end
162

163
"""
164
    load_package_from_cache_into_store!(ssp::SymbolServerInstance, uuid, store)
165

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

202 10
function clear_disc_store(ssi::SymbolServerInstance)
203 35
    for f in readdir(ssi.store_path)
204 35
        if occursin(f, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
205 35
            rm(joinpath(ssi.store_path, f), recursive = true)
206
        end
207
    end
208
end
209

210
const stdlibs = load_core()
211

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

222
end # module

Read our documentation on viewing source code .

Loading