1 25
module SymbolServer
2

3
export SymbolServerInstance, getstore
4

5
using Serialization, 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

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

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

24
function getstore(ssi::SymbolServerInstance, environment_path::AbstractString, progress_callback=nothing, error_handler=nothing)
25 30
    !ispath(environment_path) && return :success, recursive_copy(stdlibs)
26

27 30
    jl_cmd = joinpath(Sys.BINDIR, Base.julia_exename())
28 30
    server_script = joinpath(@__DIR__, "server.jl")
29

30 30
    env_to_use = copy(ENV)
31 30
    env_to_use["JULIA_REVISE"] = "manual" # Try to make sure Revise isn't enabled.
32

33 30
    if ssi.depot_path == ""
34 30
        delete!(env_to_use, "JULIA_DEPOT_PATH")
35
    else
36 0
        env_to_use["JULIA_DEPOT_PATH"] = ssi.depot_path
37
    end
38

39 30
    stderr_for_client_process = VERSION < v"1.1.0" ? nothing : IOBuffer()
40

41 30
    if ssi.process !== nothing
42 25
        to_cancel_p = ssi.process
43 30
        ssi.process = nothing
44 30
        push!(ssi.canceled_processes, to_cancel_p)
45 30
        kill(to_cancel_p)
46
    end
47

48 25
    use_code_coverage = Base.JLOptions().code_coverage
49

50 30
    currently_loading_a_package = false
51 30
    current_package_name = ""
52

53 28
    pipename = Sys.iswindows() ? "\\\\.\\pipe\\vscjlsymserv-$(UUIDs.uuid4())" : joinpath(tempdir(), "vscjlsymserv-$(UUIDs.uuid4())")
54

55 25
    server_is_ready = Channel(1)
56

57 25
    @async try
58 30
        server = Sockets.listen(pipename)
59

60 30
        put!(server_is_ready, nothing)
61

62 30
        conn = Sockets.accept(server)
63

64 30
        s = readline(conn)
65

66 30
        while s != "" && isopen(conn)
67 25
            parts = split(s, ';')
68 30
            if parts[1] == "STARTLOAD"
69 30
                currently_loading_a_package = true
70 30
                current_package_name = parts[2]
71 25
                current_package_uuid = parts[3]
72 25
                current_package_version = parts[4]
73 30
                progress_callback !== nothing && progress_callback(current_package_name)
74 30
            elseif parts[1] == "STOPLOAD"
75 30
                currently_loading_a_package = false
76 30
            elseif parts[1] == "PROCESSPKG"
77 30
                progress_callback !== nothing && progress_callback(parts[2])
78
            else
79 0
                error("Unknown command.")
80
            end
81 30
            s = readline(conn)
82
        end
83
    catch err
84 0
        bt = catch_backtrace()
85 0
        if error_handler !== nothing
86 0
            error_handler(err, bt)
87
        else
88 0
            Base.display_error(stderr, err, bt)
89
        end
90
    end
91

92 25
    take!(server_is_ready)
93

94 30
    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)
95 25
    ssi.process = p
96

97 30
    if success(p)
98
        # Now we create a new symbol store and load everything into that
99
        # from disc
100 30
        new_store = recursive_copy(stdlibs)
101 30
        load_project_packages_into_store!(ssi, environment_path, new_store)
102

103 30
        return :success, new_store
104 29
    elseif p in ssi.canceled_processes
105 24
        delete!(ssi.canceled_processes, p)
106

107 29
        return :canceled, nothing
108
    else
109 0
        if currently_loading_a_package
110 0
            return :package_load_crash, (package_name = current_package_name, stderr = stderr_for_client_process)
111
        else
112 0
            return :failure, stderr_for_client_process
113
        end
114
    end
115
end
116

117
function load_project_packages_into_store!(ssi::SymbolServerInstance, environment_path, store)
118 30
    project_filename = isfile(joinpath(environment_path, "JuliaProject.toml")) ? joinpath(environment_path, "JuliaProject.toml") : joinpath(environment_path, "Project.toml")
119 30
    project = try
120 30
        Pkg.API.read_project(project_filename)
121
    catch err
122 0
        if err isa Pkg.Types.PkgError
123 0
            @warn "Could not load project."
124 0
            return
125
        else
126 0
            rethrow(err)
127
        end
128
    end
129

130 30
    manifest_filename = isfile(joinpath(environment_path, "JuliaManifest.toml")) ? joinpath(environment_path, "JuliaManifest.toml") : joinpath(environment_path, "Manifest.toml")
131 30
    manifest = try
132 30
        Pkg.API.read_manifest(joinpath(environment_path, "Manifest.toml"))
133
    catch err
134 0
        if err isa Pkg.Types.PkgError
135 0
            @warn "Could not load manifest."
136 0
            return
137
        else
138 0
            rethrow(err)
139
        end
140
    end
141

142 30
    for uuid in values(deps(project))
143 30
        load_package_from_cache_into_store!(ssi, uuid, manifest, store)
144
    end
145
end
146

147
"""
148
    load_package_from_cache_into_store!(ssp::SymbolServerInstance, uuid, store)
149

150
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.
151
"""
152
function load_package_from_cache_into_store!(ssi::SymbolServerInstance, uuid, manifest, store)
153 30
    filename = get_filename_from_name(manifest, uuid)
154

155 30
    filename === nothing && return
156

157 30
    cache_path = joinpath(ssi.store_path, filename)
158

159 30
    if !isinmanifest(manifest, uuid)
160 0
        @warn "Tried to load $uuid but failed to find it in the manifest."
161 0
        return
162
    end
163

164 30
    pe = frommanifest(manifest, uuid)
165 30
    pe_name = packagename(manifest, uuid)
166

167 30
    haskey(store, Symbol(pe_name)) && return
168

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

195
function clear_disc_store(ssi::SymbolServerInstance)
196 30
    for f in readdir(ssi.store_path)
197 30
        if endswith(f, ".jstore")
198 30
            rm(joinpath(ssi.store_path, f))
199
        end
200
    end
201
end
202

203
const stdlibs = load_core()
204

205
function _precompile_()
206 10
    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing
207 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})
208 10
    Base.precompile(Tuple{typeof(SymbolServer.cache_methods),Any,Dict{Symbol,SymbolServer.ModuleStore}})
209 10
    Base.precompile(Tuple{typeof(SymbolServer.getenvtree)})
210 10
    Base.precompile(Tuple{typeof(SymbolServer.symbols),Dict{Symbol,SymbolServer.ModuleStore}})
211 10
    Base.precompile(Tuple{typeof(copy),Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,typeof(SymbolServer._parameter),Tuple{NTuple{4,Symbol}}}})
212
end
213
VERSION >= v"1.4.2" && _precompile_()
214

215
end # module

Read our documentation on viewing source code .

Loading