Skip to content
This repository has been archived by the owner on May 5, 2022. It is now read-only.

Implement "minimal version selection" for dependencies #5

Merged
merged 1 commit into from
Jul 5, 2021
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
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# pacman

A package manager for Dylan. (This is a library, not the name of an executable,
so there is no conflict with the `pacman` package manager for Arch Linux.)
A package manager for Dylan. (This is a library, not the name of an executable, so there
is no conflict with the `pacman` package manager for Arch Linux.)

Find, download, install, and uninstall any version of any Dylan package and its dependencies.
Find, download, install, and uninstall any version of any Dylan package and its
dependencies.

It isn't normally necessary to use this library directly. See the
[dylan-tool](https://github.com/cgay/dylan-tool) documentation instead.
[dylan-tool](https://github.com/dylan-lang/dylan-tool) documentation instead.

See [this document](https://docs.google.com/document/d/13G6I1P2v9sULeV38pjOy-5EGhJme7BDQ52jQ0gO1peM/edit?usp=sharing)
for some design discussion.

See also: the [pacman-catalog](http://github.com/cgay/pacman-catalog) repository
See also: the [pacman-catalog](http://github.com/dylan-lang/pacman-catalog) repository
100 changes: 44 additions & 56 deletions catalog.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ end function;
// Packages with no category are categorized thusly.
define constant $uncategorized = "Uncategorized";

define constant $catalog-package-release :: <release>
define constant $pacman-catalog-release :: <release>
= begin
let releases = make(<istring-table>);
let releases = make(<stretchy-vector>);
let package = make(<package>,
name: "pacman-catalog",
releases: releases,
Expand All @@ -54,10 +54,10 @@ define constant $catalog-package-release :: <release>
category: $uncategorized);
let release = make(<release>,
package: package,
version: $head,
version: make(<branch-version>, branch: "master"),
location: "https://github.com/cgay/pacman-catalog",
deps: as(<dep-vector>, #[]));
releases[$head-name] := release;
add!(releases, release);
release
end;

Expand All @@ -81,8 +81,14 @@ define function find-package
end function;

// Loading the catalog once per session should be enough, so cache it here.
define variable *catalog* :: false-or(<catalog>) = #f;
// This is a thread-local variable so that we can bind it to a dummy catalog
// while installing the catalog package itself, to prevent infinite recursion.
define thread variable *catalog* :: false-or(<catalog>) = #f;

// Load the package catalog. First look in the local cache, then download from
// GitHub. If $DYLAN_CATALOG is set then that file is used and no attempt is
// made to download the latest catalog.
//
// TODO: handle type errors (e.g., from assumptions that the json is valid)
// and return <catalog-error>.
define function load-catalog
Expand All @@ -93,15 +99,21 @@ define function load-catalog
let override = os/getenv($catalog-env-var);
let local-path
= if (override)
log-warning("Using override catalog from $%s: %s", $catalog-env-var, override);
as(<file-locator>, override)
else
merge-locators(as(<file-locator>, $local-catalog-filename),
package-manager-directory());
end;
*catalog*
:= begin
if (install($catalog-package-release, force?: too-old?(local-path)))
copy-to-local-cache($catalog-package-release, local-path);
// We pass deps?: #f here to prevent infinite recursion when
// load-catalog is called again. pacman-catalog is a data-only
// package and will never have any deps.
if (~override & install($pacman-catalog-release,
force?: too-old?(local-path),
deps?: #f))
copy-to-local-cache($pacman-catalog-release, local-path);
end;
load-local-catalog(local-path)
end
Expand Down Expand Up @@ -189,9 +201,11 @@ define function json-to-catalog
category: optional-element(name, attributes, "category", <string>)
| $uncategorized,
keywords: optional-element(name, attributes, "keywords", <seq>)
| #[]);
| #[],
releases: make(<stretchy-vector>));
json-to-releases(package,
required-element(name, attributes, "releases", <table>));
sort!(package.package-releases, test: \>);
packages[name] := package;
nreleases := nreleases + package.package-releases.size;
end if;
Expand All @@ -201,29 +215,35 @@ define function json-to-catalog
nreleases)
end function;

// json-to-releases parses all the releases for a package and stores them in
// package.package-releases. This side-effecting style is necessary because of
// the circular data structure: packages have releases with back-pointers to
// the package. (Or we could make the package-releases slot non-constant.)
define function json-to-releases
(package :: <package>, attributes :: <string-table>)
=> ()
let name = package.package-name;
let releases = package.package-releases;
let seen = make(<string-table>);
for (release-attributes keyed-by vstring in attributes)
// TODO: This test will fail when I make "1.2" parse the same as "1.2.0"
if (element(releases, vstring, default: #f))
catalog-error("duplicate package version: %s %s", name, vstring);
end;
// Canonicalize the version string.
let version = string-to-version(vstring);
if (version = $latest)
catalog-error("version 'latest' is not a valid package version in the catalog;"
" it's only valid for lookup. Did you mean 'head'?");
" specify a semantic version instead.");
end;
let version-string = version-to-string(version);
if (element(seen, version-string, default: #f))
catalog-error("duplicate release version: %s@%s", name, vstring);
end;
releases[vstring]
:= make(<release>,
add!(releases,
make(<release>,
version: version,
deps: map-as(<dep-vector>,
string-to-dep,
required-element(name, release-attributes, "deps", <seq>)),
location: required-element(name, release-attributes, "location", <string>),
package: package);
package: package));
end for;
end function;

Expand Down Expand Up @@ -274,59 +294,27 @@ define method find-package-release
=> (p :: false-or(<release>))
let package = find-package(cat, name);
let releases = package & package.package-releases;
if (releases & releases.size > 0)
let newest-first = sort(value-sequence(releases),
test: method (r1 :: <release>, r2 :: <release>)
r1.release-version > r2.release-version
end);
let latest = newest-first[0];
// The concept of "latest" doesn't mean much if it always returns
// the "head" version, which is latest by definition. So make sure
// to only return the "head" version if there are no numbered
// versions.
if (latest.release-version = $head & newest-first.size > 1)
latest := newest-first[1];
end;
latest
if ((releases | #[]).size > 0) // does 0 releases even make sense?
releases[0]
end
end method;

define method find-package-release
(cat :: <catalog>, name :: <string>, ver :: <version>)
=> (p :: false-or(<release>))
let package = find-package(cat, name);
package & find-release(package, ver)
package & find-release(package, ver, exact?: #t)
end method;

// Signal <catalog-error> if there are any problems found in the catalog.
// Signal an indirect instance of <package-error> if there are any problems found in the
// catalog.
define function validate-catalog
(cat :: <catalog>) => ()
// A reusable memoization cache (release => result).
let cache = make(<table>);
for (package keyed-by name in cat.all-packages)
for (release keyed-by vstring in package.package-releases)
validate-deps(cat, release);
end;
end;
end function;

// Verify that all dependencies specified in the catalog also exist as packages
// themselves in the catalog. Note this has nothing to do with whether or not
// they're installed.
define function validate-deps
(cat :: <catalog>, release :: <release>) => ()
local
method dep-missing (dep)
catalog-error("for package %s/%s, dependency %s is missing from the catalog",
release.package-name,
version-to-string(release.release-version),
dep-to-string(dep));
resolve-deps(release, cat, cache: cache);
end;
for (dep in release.release-deps)
let package = find-package(cat, dep.package-name);
package | dep-missing(dep);
any?(method (release)
satisfies?(dep, release.release-version)
end,
package.package-releases)
| dep-missing(dep);
end;
end function;
Loading