-
-
Notifications
You must be signed in to change notification settings - Fork 296
Expand file tree
/
Copy pathutils.jl
More file actions
217 lines (195 loc) · 7.13 KB
/
utils.jl
File metadata and controls
217 lines (195 loc) · 7.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# "Precompiling" is the longest operation
const pkgstyle_indent = textwidth(string(:Precompiling))
function printpkgstyle(io::IO, cmd::Symbol, text::String, ignore_indent::Bool = false; color = :green)
indent = ignore_indent ? 0 : pkgstyle_indent
return @lock io begin
printstyled(io, lpad(string(cmd), indent), color = color, bold = true)
println(io, " ", text)
end
end
function linewrap(str::String; io = stdout_f(), padding = 0, width = Base.displaysize(io)[2])
text_chunks = split(str, ' ')
lines = String[""]
for chunk in text_chunks
new_line_attempt = string(last(lines), chunk, " ")
if length(strip(new_line_attempt)) > width - padding
lines[end] = strip(last(lines))
push!(lines, string(chunk, " "))
else
lines[end] = new_line_attempt
end
end
return lines
end
const URL_regex = r"((file|git|ssh|http(s)?)|([\w\-\.]+@[\w\-\.]+))(:(//)?)([\w\.@\:/\-~]+)(\.git)?(/)?"x
isurl(r::String) = occursin(URL_regex, r)
stdlib_path(stdlib::String) = joinpath(Sys.STDLIB, stdlib)
function pathrepr(path::String)
# print stdlib paths as @stdlib/Name
if startswith(path, Sys.STDLIB)
path = "@stdlib/" * basename(path)
end
return "`" * Base.contractuser(path) * "`"
end
"""
normalize_path_for_toml(path::String)
Normalize a path for writing to TOML files (Project.toml/Manifest.toml).
On Windows, converts relative paths to use forward slashes for cross-platform compatibility.
Absolute paths are left unchanged as they are platform-specific by nature.
"""
function normalize_path_for_toml(path::String)
if Sys.iswindows() && !isabspath(path)
return join(splitpath(path), "/")
end
return path
end
function set_readonly(path)
for (root, dirs, files) in walkdir(path)
for file in files
filepath = joinpath(root, file)
# `chmod` on a link would change the permissions of the target. If
# the link points to a file within the same root, it will be
# chmod'ed anyway, but we don't want to make directories read-only.
# It's better not to mess with the other cases (links to files
# outside of the root, links to non-file/non-directories, etc...)
islink(filepath) && continue
fmode = filemode(filepath)
@static if Sys.iswindows()
if Sys.isexecutable(filepath)
fmode |= 0o111
end
end
try
chmod(filepath, fmode & (typemax(fmode) ⊻ 0o222))
catch
end
end
end
return nothing
end
set_readonly(::Nothing) = nothing
"""
mv_temp_dir_retries(temp_dir::String, new_path::String; set_permissions::Bool=true)::Nothing
Either rename the directory at `temp_dir` to `new_path` and set it to read-only
or if `new_path` already exists try to do nothing. Both `temp_dir` and `new_path` must
be on the same filesystem.
"""
function mv_temp_dir_retries(temp_dir::String, new_path::String; set_permissions::Bool = true)::Nothing
# Sometimes a rename can fail because the temp_dir is locked by
# anti-virus software scanning the new files.
# In this case we want to sleep and try again.
# I am using the list of error codes to retry from:
# https://github.com/isaacs/node-graceful-fs/blob/234379906b7d2f4c9cfeb412d2516f42b0fb4953/polyfills.js#L87
# Retry for up to about 60 seconds by retrying 20 times with exponential backoff.
retry = 0
max_num_retries = 20 # maybe this should be configurable?
sleep_amount = 0.01 # seconds
max_sleep_amount = 5.0 # seconds
while true
isdir(new_path) && return
# This next step is like
# `mv(temp_dir, new_path)`.
# However, `mv` defaults to `cp` if `rename` returns an error.
# `cp` is not atomic, so avoid the potential of calling it.
err = ccall(:jl_fs_rename, Int32, (Cstring, Cstring), temp_dir, new_path)
if err ≥ 0
if set_permissions
# rename worked
new_path_mode = filemode(dirname(new_path))
if Sys.iswindows()
# If this is Windows, ensure the directory mode is executable,
# as `filemode()` is incomplete. Some day, that may not be the
# case, there exists a test that will fail if this is changes.
new_path_mode |= 0o111
end
chmod(new_path, new_path_mode)
set_readonly(new_path)
end
return
else
# Ignore rename error if `new_path` exists.
isdir(new_path) && return
if retry < max_num_retries && err ∈ (Base.UV_EACCES, Base.UV_EPERM, Base.UV_EBUSY)
sleep(sleep_amount)
sleep_amount = min(sleep_amount * 2.0, max_sleep_amount)
retry += 1
else
Base.uv_error("rename of $(repr(temp_dir)) to $(repr(new_path))", err)
end
end
end
return
end
# try to call realpath on as much as possible
function safe_realpath(path)
if ispath(path)
try
return realpath(path)
catch
return path
end
end
a, b = splitdir(path)
# path cannot be reduced at the root or drive, avoid stack overflow
isempty(b) && return path
return joinpath(safe_realpath(a), b)
end
# Windows sometimes throw on `isdir`...
function isdir_nothrow(path::String)
return try
isdir(path)
catch e
false
end
end
function isfile_nothrow(path::String)
return try
isfile(path)
catch e
false
end
end
"""
atomic_toml_write(path::String, data; kws...)
Write TOML data to a file atomically by first writing to a temporary file and then moving it into place.
This prevents "teared" writes if the process is interrupted or if multiple processes write to the same file.
The `kws` are passed to `TOML.print`.
"""
function atomic_toml_write(path::String, data; kws...)
dir = dirname(path)
isempty(dir) && (dir = pwd())
temp_path, temp_io = mktemp(dir)
return try
TOML.print(temp_io, data; kws...)
close(temp_io)
mv(temp_path, path; force = true)
catch
close(temp_io)
rm(temp_path; force = true)
rethrow()
end
end
## ordering of UUIDs ##
if VERSION < v"1.2.0-DEV.269" # Defined in Base as of #30947
Base.isless(a::UUID, b::UUID) = a.value < b.value
end
function discover_repo(path::AbstractString)
dir = abspath(path)
stop_dir = homedir()
depot = Pkg.depots1()
while true
dir == depot && return nothing
gitdir = joinpath(dir, ".git")
if isdir(gitdir) || isfile(gitdir)
return dir
end
dir == stop_dir && return nothing
parent = dirname(dir)
parent == dir && return nothing
dir = parent
end
return
end
# Resolve a manifest-relative path to an absolute path
# Note: Despite the name "manifest_rel_path", this resolves relative to the manifest file
manifest_rel_path(env, path::String) = normpath(joinpath(dirname(env.manifest_file), path))