-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathfix-up.ts
More file actions
251 lines (230 loc) · 7.62 KB
/
fix-up.ts
File metadata and controls
251 lines (230 loc) · 7.62 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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
import { hooks, utils, Path, Package, Installation } from "pkgx"
import { Config } from "brewkit/config.ts"
const { usePantry } = hooks
const { host } = utils
export default async function finish(config: Config) {
const prefix = config.path.install
const yml = await usePantry().project(config.pkg).yaml()
const skip = yml.build.skip ?? []
const skips = typeof skip === 'string' ? [skip] : skip
await fix_rpaths(prefix, config.pkg, config.path.cache, config.deps.gas, skips)
await fix_pc_files(prefix, config.path.build_install)
await fix_cmake_files(prefix, config.path.build_install)
if (!skips.includes('libtool-cleanup')) {
await remove_la_files(prefix)
} else {
console.info(`skipping libtool cleanup for ${config.pkg.project}`)
}
if (host().platform == 'linux') {
await consolidate_lib64(prefix)
}
if (!skips.includes('flatten-includes')) {
await flatten_headers(prefix)
} else {
console.info(`skipping header flattening for ${config.pkg.project}`)
}
}
//////////////////////////////////////////////////////////////////////////////////////
async function fix_rpaths(pkg_prefix: Path, pkg: Package, cache: Path, deps: Installation[], skips: string[]) {
const bindir = new Path(new URL(import.meta.url).pathname).join("../../bin")
switch (host().platform) {
case 'darwin': {
if (skips.includes('fix-machos')) {
console.info(`skipping rpath fixes for ${pkg.project}`)
break
}
const proc = new Deno.Command(bindir.join('fix-machos.rb').string, {
args: [
pkg_prefix.string,
...['bin', 'sbin', 'tbin', 'lib', 'libexec'].compact(x => pkg_prefix.join(x).isDirectory()?.string)
],
env: {
GEM_HOME: cache.join('brewkit/gem').string
}
}).spawn()
const { success } = await proc.status
if (!success) throw new Error("failed to fix machos")
} break
case 'linux': {
if (skips.includes('fix-patchelf')) {
console.info(`skipping rpath fixes for ${pkg.project}`)
break
}
const proc = new Deno.Command(bindir.join('fix-elf.ts').string, {
args: [
pkg_prefix.string,
...deps.map(({ path }) => path.string)
]
}).spawn()
const { success } = await proc.status
if (!success) Deno.exit(1)
break
}}
}
async function fix_pc_files(pkg_prefix: Path, build_prefix: Path) {
//NOTE currently we only support pc files in lib/pkgconfig
// we aim to standardize on this but will relent if a package is found
// that uses share and other tools that build against it only accept that
for (const part of ["share", "lib"]) {
const d = pkg_prefix.join(part, "pkgconfig").isDirectory()
if (!d) continue
for await (const [path, { isFile }] of d.ls()) {
if (isFile && path.extname() == ".pc") {
const orig = await path.read()
const relative_path = pkg_prefix.relative({ to: path.parent() })
// newer versions of brewkit append +brewing to the path; this will get both
// variants
const text = orig
.replaceAll(build_prefix.string, `\${pcfiledir}/${relative_path}`)
.replaceAll(pkg_prefix.string, `\${pcfiledir}/${relative_path}`)
if (orig !== text) {
console.log({ fixing: path })
path.write({text, force: true})
}
}
}
}
}
async function fix_cmake_files(pkg_prefix: Path, build_prefix: Path) {
// Facebook and others who use CMake sometimes rely on a libary's .cmake files
// being shipped with it. This would be fine, except they have hardcoded paths.
// But a simple solution has been found.
const cmake = pkg_prefix.join("lib", "cmake")
if (cmake.isDirectory()) {
for await (const [path, { isFile }] of cmake.walk()) {
if (isFile && path.extname() == ".cmake") {
const orig = await path.read()
const relative_path = pkg_prefix.relative({ to: path.parent() })
// newer versions of brewkit append +brewing to the path; this will get both
// variants
const text = orig
.replaceAll(build_prefix.string, `\${CMAKE_CURRENT_LIST_DIR}/${relative_path}`)
.replaceAll(pkg_prefix.string, `\${CMAKE_CURRENT_LIST_DIR}/${relative_path}`)
if (orig !== text) {
console.log({ fixing: path })
path.write({text, force: true})
}
}
}
}
}
async function remove_la_files(pkg_prefix: Path) {
// libtool .la files contain hardcoded paths and cause more problems than they solve
// only remove top-level lib/*.la — subdirectory .la files may be module descriptors
// needed at runtime (eg. ImageMagick codec plugins)
const lib = pkg_prefix.join("lib").isDirectory()
if (!lib) return
for await (const [path, { isFile }] of lib.ls()) {
if (isFile && path.extname() == ".la") {
console.log({ removing: path })
Deno.removeSync(path.string)
}
}
}
async function consolidate_lib64(pkg_prefix: Path) {
// some build systems install to lib64 on x86-64 Linux; we standardize on lib
const lib64 = pkg_prefix.join("lib64")
if (!lib64.isDirectory()) return
const lib = pkg_prefix.join("lib")
Deno.mkdirSync(lib.string, { recursive: true })
for await (const [path, { isFile, isSymlink }] of lib64.ls()) {
const dest = lib.join(path.basename())
if (isFile || isSymlink) {
Deno.renameSync(path.string, dest.string)
}
}
Deno.removeSync(lib64.string, { recursive: true })
Deno.symlinkSync("lib", lib64.string)
}
// headers that must not be flattened into include/ as they would shadow
// system/libc headers (compared case-insensitively for macOS HFS+/APFS)
const SYSTEM_HEADERS = new Set([
"assert.h",
"complex.h",
"ctype.h",
"errno.h",
"fenv.h",
"float.h",
"inttypes.h",
"iso646.h",
"limits.h",
"locale.h",
"math.h",
"setjmp.h",
"signal.h",
"stdalign.h",
"stdarg.h",
"stdatomic.h",
"stdbool.h",
"stddef.h",
"stdint.h",
"stdio.h",
"stdlib.h",
"stdnoreturn.h",
"string.h",
"tgmath.h",
"threads.h",
"time.h",
"uchar.h",
"wchar.h",
"wctype.h",
// POSIX
"dirent.h",
"fcntl.h",
"glob.h",
"grp.h",
"netdb.h",
"poll.h",
"pthread.h",
"pwd.h",
"regex.h",
"sched.h",
"search.h",
"semaphore.h",
"spawn.h",
"strings.h",
"syslog.h",
"termios.h",
"unistd.h",
"utime.h",
"wordexp.h",
// common C++ / platform headers that cause trouble
"memory.h",
"version.h",
"module.h",
])
async function flatten_headers(pkg_prefix: Path) {
// if include/ contains exactly one subdirectory and no loose files, flatten it
// eg. include/foo/*.h → include/*.h with include/foo → symlink to .
const include = pkg_prefix.join("include").isDirectory()
if (!include) return
const entries: Path[] = []
const subdirs: Path[] = []
for await (const [path, { isDirectory }] of include.ls()) {
entries.push(path)
if (isDirectory) subdirs.push(path)
}
if (subdirs.length == 1 && entries.length == 1) {
const subdir = subdirs[0]
const name = subdir.basename()
// check for headers that would shadow system headers (case-insensitive for macOS)
const dominated: string[] = []
for await (const [path] of subdir.ls()) {
const lower = path.basename().toLowerCase()
if (SYSTEM_HEADERS.has(lower)) {
dominated.push(path.basename())
}
}
if (dominated.length > 0) {
console.log({ skipping_flatten: name, would_shadow: dominated })
return
}
// move all contents up
for await (const [path] of subdir.ls()) {
Deno.renameSync(path.string, include.join(path.basename()).string)
}
Deno.removeSync(subdir.string, { recursive: true })
Deno.symlinkSync(".", include.join(name).string)
console.log({ flattened_headers: name })
}
}