Skip to content

Commit b1094a1

Browse files
committed
Use sqlite cache of pantry data
We had to vendor the deno sqlite3 library as it had no other direct way to allow us to configure the path to the sqlite library at runtime. To accomplish this we had to modify the sources somewhat also :/
1 parent 5168d49 commit b1094a1

File tree

18 files changed

+2597
-48
lines changed

18 files changed

+2597
-48
lines changed

.github/deno-to-node.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ const version = (() => {
1616
}
1717
})()
1818

19+
const mappings: Record<string, string> = {
20+
"https://deno.land/x/is_what@v4.1.15/src/index.ts": "is-what",
21+
"https://deno.land/x/outdent@v0.8.0/mod.ts": "outdent",
22+
"./src/utils/flock.deno.ts": "./src/utils/flock.node.ts",
23+
"./src/hooks/useSyncCache.ts": "./src/hooks/useSyncCache.node.ts"
24+
}
25+
26+
if (test) {
27+
mappings["./src/hooks/useSyncCache.test.ts"] = "./src/hooks/useCache.test.ts" // no other easy way to skip the test
28+
}
29+
1930
await build({
2031
entryPoints: ["./mod.ts"],
2132
outDir: "./dist",
@@ -30,11 +41,7 @@ await build({
3041
}],
3142
},
3243
importMap: "deno.json",
33-
mappings: {
34-
"https://deno.land/x/is_what@v4.1.15/src/index.ts": "is-what",
35-
"https://deno.land/x/outdent@v0.8.0/mod.ts": "outdent",
36-
"./src/utils/flock.deno.ts": "./src/utils/flock.node.ts"
37-
},
44+
mappings,
3845
package: {
3946
name: "libpkgx",
4047
version,

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ jobs:
6666
with:
6767
path: src
6868
- uses: denoland/setup-deno@v1
69-
- run: deno run --no-config --unstable src/mod.ts
69+
- run: deno run --no-config --unstable --allow-all src/mod.ts
7070

7171
dnt:
7272
runs-on: ${{ matrix.os }}

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"pkgx": "deno~1.39 npm",
1414
"tasks": {
15-
"test": "deno test --parallel --unstable --allow-env --allow-read --allow-net=dist.pkgx.dev,github.com,codeload.github.com --allow-write --allow-run=tar,uname,/bin/sh,foo,'C:\\Windows\\system32\\cmd.exe'",
15+
"test": "deno test --parallel --unstable --allow-env --allow-read --allow-ffi --allow-net=dist.pkgx.dev,github.com,codeload.github.com --allow-write --allow-run=tar,uname,/bin/sh,foo,'C:\\Windows\\system32\\cmd.exe'",
1616
"typecheck": "deno check --unstable ./mod.ts",
1717
"dnt": ".github/deno-to-node.ts"
1818
},

src/hooks/usePantry.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,10 @@ Deno.test("validatePackageRequirement - number constraint", () => {
8585
const result = validatePackageRequirement("pkgx.sh/test", 1)
8686
assertEquals(result?.constraint.toString(), "^1")
8787
})
88+
89+
Deno.test("find", async () => {
90+
useTestConfig()
91+
const foo = await usePantry().find("python@3.11")
92+
assertEquals(foo.length, 1)
93+
assertEquals(foo[0].project, "python.org")
94+
})

src/hooks/usePantry.ts

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { is_what, PlainObject } from "../deps.ts"
22
const { isNumber, isPlainObject, isString, isArray, isPrimitive, isBoolean } = is_what
33
import { Package, Installation, PackageRequirement } from "../types.ts"
4+
import { provides as cache_provides, available as cache_available, runtime_env as cache_runtime_env, companions as cache_companions, dependencies as cache_dependencies } from "./useSyncCache.ts";
45
import SemVer, * as semver from "../utils/semver.ts"
56
import useMoustaches from "./useMoustaches.ts"
67
import { PkgxError } from "../utils/error.ts"
78
import { validate } from "../utils/misc.ts"
9+
import * as pkgutils from "../utils/pkg.ts"
810
import useConfig from "./useConfig.ts"
911
import host from "../utils/host.ts"
1012
import Path from "../utils/Path.ts"
@@ -45,6 +47,7 @@ export class PantryNotFoundError extends PantryError {
4547

4648
export default function usePantry() {
4749
const prefix = useConfig().data.join("pantry/projects")
50+
const is_cache_available = cache_available() && pantry_paths().length == 1
4851

4952
async function* ls(): AsyncGenerator<LsEntry> {
5053
const seen = new Set()
@@ -78,11 +81,23 @@ export default function usePantry() {
7881
throw new PackageNotFoundError(project)
7982
})()
8083

81-
const companions = async () => parse_pkgs_node((await yaml())["companions"])
84+
const companions = async () => {
85+
if (is_cache_available) {
86+
return await cache_companions(project) ?? parse_pkgs_node((await yaml())["companions"])
87+
} else {
88+
return parse_pkgs_node((await yaml())["companions"])
89+
}
90+
}
8291

8392
const runtime_env = async (version: SemVer, deps: Installation[]) => {
84-
const yml = await yaml()
85-
const obj = validate.obj(yml["runtime"]?.["env"] ?? {})
93+
const obj = await (async () => {
94+
if (is_cache_available) {
95+
const cached = await cache_runtime_env(project)
96+
if (cached) return cached
97+
}
98+
const yml = await yaml()
99+
return validate.obj(yml["runtime"]?.["env"] ?? {})
100+
})()
86101
return expand_env_obj(obj, { project, version }, deps)
87102
}
88103

@@ -94,7 +109,13 @@ export default function usePantry() {
94109
return platforms.includes(host().platform) ||platforms.includes(`${host().platform}/${host().arch}`)
95110
}
96111

97-
const drydeps = async () => parse_pkgs_node((await yaml()).dependencies)
112+
const drydeps = async () => {
113+
if (is_cache_available) {
114+
return await cache_dependencies(project) ?? parse_pkgs_node((await yaml()).dependencies)
115+
} else {
116+
return parse_pkgs_node((await yaml()).dependencies)
117+
}
118+
}
98119

99120
const provides = async () => {
100121
let node = (await yaml())["provides"]
@@ -164,6 +185,27 @@ export default function usePantry() {
164185
async function find(name: string) {
165186
type Foo = ReturnType<typeof project> & LsEntry
166187

188+
//lol FIXME
189+
name = pkgutils.parse(name).project
190+
191+
if (prefix.join(name).isDirectory()) {
192+
const foo = project(name)
193+
return [{...foo, project: name }]
194+
}
195+
196+
/// only use cache if PKGX_PANTRY_PATH is not set
197+
if (is_cache_available) {
198+
const cached = await cache_provides(name)
199+
if (cached?.length) {
200+
return cached.map(x => ({
201+
...project(x),
202+
project: x
203+
}))
204+
}
205+
206+
// else we need to still check for display-names
207+
}
208+
167209
name = name.toLowerCase()
168210

169211
//TODO not very performant due to serial awaits
@@ -234,7 +276,8 @@ export default function usePantry() {
234276
parse_pkgs_node,
235277
expand_env_obj,
236278
missing,
237-
neglected
279+
neglected,
280+
pantry_paths
238281
}
239282

240283
function pantry_paths(): Path[] {

src/hooks/useSync.test.ts

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,53 @@
1+
import specimen, { _internals } from "./useSync.ts"
12
import { useTestConfig } from "./useTestConfig.ts"
3+
import * as mock from "deno/testing/mock.ts"
24
import { assert } from "deno/assert/mod.ts"
35
import usePantry from "./usePantry.ts"
4-
import useSync from "./useSync.ts"
56

67
// NOTE actually syncs from github
78
// TODO unit tests should not do actual network calls, instead make an implementation suite
89

910
Deno.test("useSync", async runner => {
10-
await runner.step("w/o git", async () => {
11-
const conf = useTestConfig({})
12-
usePantry().prefix.rm({ recursive: true }) // we need to delete the fixtured pantry
13-
assert(conf.git === undefined)
14-
await test()
15-
})
16-
17-
await runner.step({
18-
name: "w/git",
19-
ignore: Deno.build.os == 'windows' && !Deno.env.get("CI"),
20-
async fn() {
21-
const conf = useTestConfig({ PATH: "/usr/bin" })
11+
const stub = mock.stub(_internals, "cache", async () => {})
12+
13+
try {
14+
await runner.step("w/o git", async () => {
15+
const conf = useTestConfig({})
2216
usePantry().prefix.rm({ recursive: true }) // we need to delete the fixtured pantry
23-
assert(conf.git !== undefined)
17+
assert(conf.git === undefined)
2418
await test()
19+
})
2520

26-
// test the “already cloned” code-path
27-
await useSync()
28-
}
29-
})
30-
31-
async function test() {
32-
let errord = false
33-
try {
34-
await usePantry().project("gnu.org/gcc").available()
35-
} catch {
36-
errord = true
37-
}
38-
assert(errord, `should be no pantry but there is! ${usePantry().prefix}`)
21+
await runner.step({
22+
name: "w/git",
23+
ignore: Deno.build.os == 'windows' && !Deno.env.get("CI"),
24+
async fn() {
25+
const conf = useTestConfig({ PATH: "/usr/bin" })
26+
usePantry().prefix.rm({ recursive: true }) // we need to delete the fixtured pantry
27+
assert(conf.git !== undefined)
28+
await test()
29+
30+
// test the “already cloned” code-path
31+
await specimen()
32+
}
33+
})
3934

40-
await useSync()
35+
async function test() {
36+
let errord = false
37+
try {
38+
await usePantry().project("gnu.org/gcc").available()
39+
} catch {
40+
errord = true
41+
}
42+
assert(errord, `should be no pantry but there is! ${usePantry().prefix}`)
4143

42-
assert(await usePantry().project("gnu.org/gcc").available())
44+
await specimen()
45+
46+
assert(await usePantry().project("gnu.org/gcc").available())
47+
}
48+
49+
} finally {
50+
stub.restore()
4351
}
52+
4453
})

src/hooks/useSync.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import useDownload from "./useDownload.ts"
77
import usePantry from "./usePantry.ts"
88
import useConfig from "./useConfig.ts"
99
import Path from "../utils/Path.ts"
10+
import useSyncCache from "./useSyncCache.ts";
1011

1112
//FIXME tar is fetched from PATH :/ we want control
1213
//FIXME run in general is not controllable since it delegates to the shell
1314

1415
interface Logger {
1516
syncing(path: Path): void
17+
caching(path: Path): void
1618
syncd(path: Path): void
1719
}
1820

@@ -23,6 +25,27 @@ export default async function(logger?: Logger) {
2325

2426
const unflock = await flock(pantry_dir.mkdir('p'))
2527

28+
try {
29+
await _internals.sync(pantry_dir)
30+
try {
31+
logger?.caching(pantry_dir)
32+
await _internals.cache()
33+
} catch (err) {
34+
console.warn("failed to cache pantry")
35+
console.error(err)
36+
}
37+
} finally {
38+
await unflock()
39+
}
40+
41+
logger?.syncd(pantry_dir)
42+
}
43+
44+
export const _internals = {
45+
sync, cache: useSyncCache
46+
}
47+
48+
async function sync(pantry_dir: Path) {
2649
try {
2750
//TODO if there was already a lock, just wait on it, don’t do the following stuff
2851

@@ -55,11 +78,7 @@ export default async function(logger?: Logger) {
5578

5679
proc.close()
5780

58-
} finally {
59-
await unflock()
6081
}
61-
62-
logger?.syncd(pantry_dir)
6382
}
6483

6584
//////////////////////// utils

src/hooks/useSyncCache.node.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// the sqlite lib we use only works in deno
2+
3+
import { PackageRequirement } from "../../mod.ts";
4+
5+
export default async function()
6+
{}
7+
8+
export function provides(_program: string): string[] {
9+
throw new Error()
10+
}
11+
12+
export function dependencies(_project: string): PackageRequirement[] {
13+
throw new Error()
14+
}
15+
16+
export function completion(_prefix: string): string[] {
17+
throw new Error()
18+
}
19+
20+
/// is the cache available?
21+
export function available(): boolean {
22+
return false
23+
}
24+
25+
export function companions(_project: string): PackageRequirement[] {
26+
throw new Error()
27+
}
28+
29+
export function runtime_env(_project: string): Record<string, string> {
30+
throw new Error()
31+
}

src/hooks/useSyncCache.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import specimen, { provides, dependencies, available, runtime_env, completion, companions } from "./useSyncCache.ts"
2+
import { useTestConfig } from "./useTestConfig.ts"
3+
import { assert, assertEquals } from "deno/assert/mod.ts"
4+
import { _internals } from "./useSync.ts"
5+
import usePantry from "./usePantry.ts"
6+
7+
// NOTE actually syncs from github
8+
// TODO unit tests should not do actual network calls, instead make an implementation suite
9+
10+
Deno.test({
11+
name: "useSyncCache",
12+
ignore: Deno.build.os == 'windows',
13+
sanitizeResources: false,
14+
async fn() {
15+
useTestConfig()
16+
await _internals.sync(usePantry().prefix.parent())
17+
await specimen()
18+
19+
//TODO test better
20+
assert(available())
21+
assertEquals((await provides('node'))?.[0], 'nodejs.org')
22+
// assertEquals((await dependencies('nodejs.org'))?.length, 3)
23+
assert(new Set(await completion('nod')).has("node"))
24+
assertEquals((await companions("nodejs.org"))?.[0]?.project, "npmjs.com")
25+
assert((await runtime_env("numpy.org"))?.["PYTHONPATH"])
26+
}
27+
})

0 commit comments

Comments
 (0)