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

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

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

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

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

41 30
    stderr_for_client_process = VERSION < v"1.1.0" ? nothing : IOBuffer()
42

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

50 25
    use_code_coverage = Base.JLOptions().code_coverage
51

52 30
    currently_loading_a_package = false
53 30
    current_package_name = ""
54

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

57 25
    server_is_ready = Channel(1)
58

59 25
    @async try
60 30
        server = Sockets.listen(pipename)
61

62 30
        put!(server_is_ready, nothing)
63

64 30
        conn = Sockets.accept(server)
65

66 30
        s = readline(conn)
67

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

94 25
    take!(server_is_ready)
95

96 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)
97 25
    ssi.process = p
98

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

105 30
        return :success, new_store
106 28
    elseif p in ssi.canceled_processes
107 24
        delete!(ssi.canceled_processes, p)
108

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

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

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

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

149
"""
150
    load_package_from_cache_into_store!(ssp::SymbolServerInstance, uuid, store)
151

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

157 30
    filename === nothing && return
158

159 30
    cache_path = joinpath(ssi.store_path, filename)
160

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

166 30
    pe = frommanifest(manifest, uuid)
167 30
    pe_name = packagename(manifest, uuid)
168

169 30
    haskey(store, Symbol(pe_name)) && return
170

171 30
    if isfile(cache_path)
172 30
        try
173 25
            package_data = open(cache_path) do io
174 10
                CacheStore.read(io)
175
            end
176 30
            store[Symbol(pe_name)] = package_data.val
177 30
            for dep in deps(pe)
178 30
                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 25
        @warn "$(pe_name) not stored on disc"
193 30
        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
function clear_disc_store(ssi::SymbolServerInstance)
198 30
    for f in readdir(ssi.store_path)
199 30
        if endswith(f, ".jstore")
200 30
            rm(joinpath(ssi.store_path, f))
201
        end
202
    end
203
end
204

205
const stdlibs = load_core()
206

207
function _precompile_()
208 10
    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing
209 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})
210 10
    Base.precompile(Tuple{typeof(SymbolServer.cache_methods),Any,Dict{Symbol,SymbolServer.ModuleStore}})
211 10
    Base.precompile(Tuple{typeof(SymbolServer.getenvtree)})
212 10
    Base.precompile(Tuple{typeof(SymbolServer.symbols),Dict{Symbol,SymbolServer.ModuleStore}})
213 10
    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