Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync cache #60

Merged
merged 1 commit into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/deno-to-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ await build({
mappings: {
"https://deno.land/x/[email protected]/src/index.ts": "is-what",
"https://deno.land/x/[email protected]/mod.ts": "outdent",
"./src/utils/flock.deno.ts": "./src/utils/flock.node.ts"
"./src/utils/flock.deno.ts": "./src/utils/flock.node.ts",
"./src/hooks/useSyncCache.ts": "./src/hooks/useSyncCache.node.ts",
"./src/hooks/useSyncCache.test.ts": "./src/hooks/useCache.test.ts" // no other easy way to skip the test
},
package: {
name: "libpkgx",
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
with:
path: src
- uses: denoland/setup-deno@v1
- run: deno run --no-config --unstable src/mod.ts
- run: deno run --no-config --unstable --allow-all src/mod.ts

dnt:
runs-on: ${{ matrix.os }}
Expand Down
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"pkgx": "deno~1.39 npm",
"tasks": {
"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'",
"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'",
"typecheck": "deno check --unstable ./mod.ts",
"dnt": ".github/deno-to-node.ts"
},
Expand Down
7 changes: 7 additions & 0 deletions src/hooks/usePantry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,10 @@ Deno.test("validatePackageRequirement - number constraint", () => {
const result = validatePackageRequirement("pkgx.sh/test", 1)
assertEquals(result?.constraint.toString(), "^1")
})

Deno.test("find", async () => {
useTestConfig()
const foo = await usePantry().find("[email protected]")
assertEquals(foo.length, 1)
assertEquals(foo[0].project, "python.org")
})
53 changes: 48 additions & 5 deletions src/hooks/usePantry.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { is_what, PlainObject } from "../deps.ts"
const { isNumber, isPlainObject, isString, isArray, isPrimitive, isBoolean } = is_what
import { Package, Installation, PackageRequirement } from "../types.ts"
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";
import SemVer, * as semver from "../utils/semver.ts"
import useMoustaches from "./useMoustaches.ts"
import { PkgxError } from "../utils/error.ts"
import { validate } from "../utils/misc.ts"
import * as pkgutils from "../utils/pkg.ts"
import useConfig from "./useConfig.ts"
import host from "../utils/host.ts"
import Path from "../utils/Path.ts"
Expand Down Expand Up @@ -45,6 +47,7 @@ export class PantryNotFoundError extends PantryError {

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

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

const companions = async () => parse_pkgs_node((await yaml())["companions"])
const companions = async () => {
if (is_cache_available) {
return await cache_companions(project) ?? parse_pkgs_node((await yaml())["companions"])
} else {
return parse_pkgs_node((await yaml())["companions"])
}
}

const runtime_env = async (version: SemVer, deps: Installation[]) => {
const yml = await yaml()
const obj = validate.obj(yml["runtime"]?.["env"] ?? {})
const obj = await (async () => {
if (is_cache_available) {
const cached = await cache_runtime_env(project)
if (cached) return cached
}
const yml = await yaml()
return validate.obj(yml["runtime"]?.["env"] ?? {})
})()
return expand_env_obj(obj, { project, version }, deps)
}

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

const drydeps = async () => parse_pkgs_node((await yaml()).dependencies)
const drydeps = async () => {
if (is_cache_available) {
return await cache_dependencies(project) ?? parse_pkgs_node((await yaml()).dependencies)
} else {
return parse_pkgs_node((await yaml()).dependencies)
}
}

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

//lol FIXME
name = pkgutils.parse(name).project

if (prefix.join(name).isDirectory()) {
const foo = project(name)
return [{...foo, project: name }]
}

/// only use cache if PKGX_PANTRY_PATH is not set
if (is_cache_available) {
const cached = await cache_provides(name)
if (cached?.length) {
return cached.map(x => ({
...project(x),
project: x
}))
}

// else we need to still check for display-names
}

name = name.toLowerCase()

//TODO not very performant due to serial awaits
Expand Down Expand Up @@ -234,7 +276,8 @@ export default function usePantry() {
parse_pkgs_node,
expand_env_obj,
missing,
neglected
neglected,
pantry_paths
}

function pantry_paths(): Path[] {
Expand Down
67 changes: 38 additions & 29 deletions src/hooks/useSync.test.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,53 @@
import specimen, { _internals } from "./useSync.ts"
import { useTestConfig } from "./useTestConfig.ts"
import * as mock from "deno/testing/mock.ts"
import { assert } from "deno/assert/mod.ts"
import usePantry from "./usePantry.ts"
import useSync from "./useSync.ts"

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

Deno.test("useSync", async runner => {
await runner.step("w/o git", async () => {
const conf = useTestConfig({})
usePantry().prefix.rm({ recursive: true }) // we need to delete the fixtured pantry
assert(conf.git === undefined)
await test()
})

await runner.step({
name: "w/git",
ignore: Deno.build.os == 'windows' && !Deno.env.get("CI"),
async fn() {
const conf = useTestConfig({ PATH: "/usr/bin" })
const stub = mock.stub(_internals, "cache", async () => {})

try {
await runner.step("w/o git", async () => {
const conf = useTestConfig({})
usePantry().prefix.rm({ recursive: true }) // we need to delete the fixtured pantry
assert(conf.git !== undefined)
assert(conf.git === undefined)
await test()
})

// test the “already cloned” code-path
await useSync()
}
})

async function test() {
let errord = false
try {
await usePantry().project("gnu.org/gcc").available()
} catch {
errord = true
}
assert(errord, `should be no pantry but there is! ${usePantry().prefix}`)
await runner.step({
name: "w/git",
ignore: Deno.build.os == 'windows' && !Deno.env.get("CI"),
async fn() {
const conf = useTestConfig({ PATH: "/usr/bin" })
usePantry().prefix.rm({ recursive: true }) // we need to delete the fixtured pantry
assert(conf.git !== undefined)
await test()

// test the “already cloned” code-path
await specimen()
}
})

await useSync()
async function test() {
let errord = false
try {
await usePantry().project("gnu.org/gcc").available()
} catch {
errord = true
}
assert(errord, `should be no pantry but there is! ${usePantry().prefix}`)

assert(await usePantry().project("gnu.org/gcc").available())
await specimen()

assert(await usePantry().project("gnu.org/gcc").available())
}

} finally {
stub.restore()
}

})
27 changes: 23 additions & 4 deletions src/hooks/useSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import useDownload from "./useDownload.ts"
import usePantry from "./usePantry.ts"
import useConfig from "./useConfig.ts"
import Path from "../utils/Path.ts"
import useSyncCache from "./useSyncCache.ts";

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

interface Logger {
syncing(path: Path): void
caching(path: Path): void
syncd(path: Path): void
}

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

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

try {
await _internals.sync(pantry_dir)
try {
logger?.caching(pantry_dir)
await _internals.cache()
} catch (err) {
console.warn("failed to cache pantry")
console.error(err)
}
} finally {
await unflock()
}

logger?.syncd(pantry_dir)
}

export const _internals = {
sync, cache: useSyncCache
}

async function sync(pantry_dir: Path) {
try {
//TODO if there was already a lock, just wait on it, don’t do the following stuff

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

proc.close()

} finally {
await unflock()
}

logger?.syncd(pantry_dir)
}

//////////////////////// utils
Expand Down
31 changes: 31 additions & 0 deletions src/hooks/useSyncCache.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// the sqlite lib we use only works in deno

import { PackageRequirement } from "../../mod.ts";

export default async function()
{}

export function provides(_program: string): string[] {
throw new Error()
}

export function dependencies(_project: string): PackageRequirement[] {
throw new Error()
}

export function completion(_prefix: string): string[] {
throw new Error()
}

/// is the cache available?
export function available(): boolean {
return false
}

export function companions(_project: string): PackageRequirement[] {
throw new Error()
}

export function runtime_env(_project: string): Record<string, string> {
throw new Error()
}
27 changes: 27 additions & 0 deletions src/hooks/useSyncCache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import specimen, { provides, dependencies, available, runtime_env, completion, companions } from "./useSyncCache.ts"
import { useTestConfig } from "./useTestConfig.ts"
import { assert, assertEquals } from "deno/assert/mod.ts"
import { _internals } from "./useSync.ts"
import usePantry from "./usePantry.ts"

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

Deno.test({
name: "useSyncCache",
ignore: Deno.build.os == 'windows',
sanitizeResources: false,
async fn() {
useTestConfig()
await _internals.sync(usePantry().prefix.parent())
await specimen()

//TODO test better
assert(available())
assertEquals((await provides('node'))?.[0], 'nodejs.org')
// assertEquals((await dependencies('nodejs.org'))?.length, 3)
assert(new Set(await completion('nod')).has("node"))
assertEquals((await companions("nodejs.org"))?.[0]?.project, "npmjs.com")
assert((await runtime_env("numpy.org"))?.["PYTHONPATH"])
}
})
Loading
Loading