Skip to content

Move dependency resolution to aurcache - #360

Open
gyscos wants to merge 8 commits into
Lukas-Heiligenbrunner:masterfrom
gyscos:feature/dependency-resolution
Open

Move dependency resolution to aurcache#360
gyscos wants to merge 8 commits into
Lukas-Heiligenbrunner:masterfrom
gyscos:feature/dependency-resolution

Conversation

@gyscos

@gyscos gyscos commented May 13, 2026

Copy link
Copy Markdown
Contributor

This moves the dependency resolution up, from paru running in the builder, to AURCache itself.

Context: base packages (PKGBUILDS) can sometimes build several packages, called "child packages". When it's just one, the child package is usually called like the base package, so we often mix up the two concepts. I'll use base package and child package precisely here.

Previous situation:

  • Packages are built with paru, which pulls and builds all AUR dependencies automatically.
  • Each entry in the DB has the name of a "child package", but in practice the entire full package is built. The entry references all package files from all child packages, and all required AUR dependencies. It is possible to add two entries for two child packages of the same base package, in which case they essentially build the same files.
  • Each package can be built entirely separately and without ordering, since they all rebuild all their dependencies.

Situation after this PR:

  • Each entry in the DB has the name of a "full package".
  • Each entry only references its own package files.
  • Each entry has a new flag that indicates if the package was directly requested. If not, it's only there as a dependency to another package.
  • A new table lists the dependencies between packages.
  • To build a base package, all its dependencies must have a build already.
  • Builders have access to AURCache's own repository in their pacman.conf (in addition to the specified mirrorlist for core/extra/multilib packages).
  • Packages are directly built with makepkg. AUR dependencies are directly pulled from AURCache's repository.

An example package used in some tests was turso, which depends on libaegis and simsimd, both of which can be built without further AUR dependencies (so it's a relatively simple dependency tree to test).

Tests

  • This PR adds a few integration tests, where the http calls to AUR are mocked.
  • The builder test bash script has been replaced with a rust script, so it can re-use the actual build command.

Notes

  • Looks like PKGBUILD and SRCINFO can include ?signed in the URL to indicate it should be signed (for git urls). makepkg is fine with it, even when it comes after a #commit. For example libaegis:
	source = git+https://github.com/aegis-aead/libaegis.git#commit=fb173f3ea32e16c1c93c43035804ba6daf3adc59?signed/

This is technically not valid URL (the ? should come before #), and the alpm-srcinfo crate doesn't support that. Ticket filed. This is currently blocking the use of that crate for this.

This is still at the draft level, I'm cleaning up and finishing some details. But the big changes/architecture should already be there.

@gyscos
gyscos marked this pull request as draft May 13, 2026 14:00
Comment on lines +12 to +14
let base_url = std::env::var("AUR_RPC_URL")
.map(|u| u.trim_end_matches('/').trim_end_matches("/rpc/v5").trim_end_matches('/').to_string())
.unwrap_or_else(|_| "https://aur.archlinux.org".to_string());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mostly to allow mocking/testing

@Lukas-Heiligenbrunner

Copy link
Copy Markdown
Owner
  2026-05-13T17:13:04.593201Z DEBUG aurcache_builder::logger: error: Partition /etc/pacman.conf is mounted read only
error: not enough free disk space
error: failed to commit transaction (not enough free disk space)

    at aurcache-builder/src/logger.rs:48

  2026-05-13T17:13:04.617698Z DEBUG aurcache_builder::logger: Errors occurred, no packages were upgraded.

    at aurcache-builder/src/logger.rs:48

  2026-05-13T17:13:04.643060Z DEBUG aurcache_builder::logger: Log buffer flushed!
    at aurcache-builder/src/logger.rs:90

  2026-05-13T17:13:04.762304Z DEBUG bollard::read: Decoding JSON line from stream: {"StatusCode":1}
    at /home/lukas/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bollard-0.20.2/src/read.rs:117

I'm getting this error when trying locally, might be a fault on my side, have to dig into it.
Previously it worked to execute aurcache binary locally for testing and use the system docker for builds...
Do you mount the pacman.conf into the builder?

@gyscos

gyscos commented May 13, 2026

Copy link
Copy Markdown
Contributor Author

Yes I'm mounting the pacman.conf, so I don't need to patch it as root/sudo when running the container.

I remember getting this error also in paru+chroot (not even in aurcache) when trying to upgrade pacman with pacman.conf mounted read-only.

If that's the case there may be a way to specifically ask pacman not to upgrade that file? Will check.

Alternatively we just can't upgrade pacman itself, and need to create a builder from scratch, which is also not that terrible. We could IgnorePkg = pacman in pacman.conf to avoid trying to upgrade pacman in-between builder image refreshes.

@gyscos

gyscos commented May 14, 2026

Copy link
Copy Markdown
Contributor Author

Starting to be in a state that's testable. I updated the UI to hide dependency-only packages from the main list.
When we get better filtering in the main package list we can consider making this filter a toggle.

The package info page now shows dependency/dependent links in the sidebar. I think it looks fine, but we can also consider reworking the inner page to share the space between the builds and the dependencies/dependents.

Remaining details to fix:

  • PGP verification in makepkg is currently disabled. Ideally we'd fetch keys similar to paru's --pgpfetch and re-enable verification. EDIT: ✔️
  • Build flags are now passed to makepkg instead of paru, which means many previous build flags are no longer valid. EDIT: ✔️

Re: mounting pacman.conf, it's also similar to how makechrootpkg handles it, with the same result that upgrading pacman is not trivial. I think makechrootpkg is where we're moving towards (if/once we have persistent builders that need to isolate packages without spawning new containers). Alternatives (for both) include:

  • Ignoring disk space checks when building/upgrading. It will fail to overwrite pacman.conf (though in practice it'll just create a pacnew instead) but will otherwise carry on undisturbed.
  • Not upgrade pacman when building packages (what this PR currently does). Since we regularly update the builder image, it's usually fine to not upgrade pacman itself every time (though it could break things like a very new paru version).
  • Use a different place for the pacman.conf we use, and tell makepkg to use that. This way /etc/pacman.conf ends up essentially unused, ready to be overwritten by a pacman upgrade. The actual pacman config could be anywhere[.
    • For makechrootpkg, mkarchroot -C /path/to/our/pacman.conf should be enough
    • For makepkg (the situation in this PR), we'd need a custom makepkg.conf which includes PACMAN=(pacman --config /path/to/our/pacman.conf, then makepkg --config /path/to/our/makepkg.conf. A bit boilerplate~y, but it could all be done outside of the builder container and mounted read-only, as it's static. Immutability ftw.

My main goal for that is to move a bit closer to not having passwordless-sudo when we build the package. Right now we still need sudo to install the dependencies, but I hope we could eventually also split this into a part that only download and installs dependencies (as root), then build the package (with no passwordless sudo). This is what makechrootpkg does internally so if we end up going there it'll simplify things.

Though I do realize now that if we're going towards makechrootpkg eventually, there's no point in trying to get there incrementally, and until then we could keep passwordless sudo, and writing the config file from the container instead of bind-mounting it. 🤷

EDIT: Okaaaay I think it's properly ready for review now. It's a big baby at +5k loc, but a good chunk of that is for tests.

@gyscos
gyscos marked this pull request as ready for review May 14, 2026 15:02
Comment on lines +4 to +18
fn fetch_required_pgp_keys_cmd() -> &'static str {
"pgp_keys=\"$(if [ -f .SRCINFO ]; then \
sed -n 's/^[[:space:]]*validpgpkeys[[:space:]]*=[[:space:]]*//p' .SRCINFO; \
else \
makepkg --printsrcinfo | sed -n 's/^[[:space:]]*validpgpkeys[[:space:]]*=[[:space:]]*//p'; \
fi)\" && \
if [ -n \"$pgp_keys\" ]; then \
while IFS= read -r key; do \
[ -n \"$key\" ] || continue; \
if ! gpg --batch --list-keys \"$key\" >/dev/null 2>&1; then \
gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys \"$key\"; \
fi; \
done <<< \"$pgp_keys\"; \
fi"
}

@gyscos gyscos May 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soo this is still a pretty ugly solution. I see a few options:

  • A) Fetch the key by looking at the SRCINFO at the last minute (current PR). We don't currently have the SRCINFO before then, so this is the earliest we can look at it. Doing it in bash is painful.
  • B) Fetch the keys by looking at the SRCINFO at the last minute, but do it in rust so it's a bit cleaner. We'd write a tiny binary using alpm-srcinfo that parses the SRCINFO and fetches the keys as needed. Sounds like quite some boilerplate to avoid a dozen lines of bash, but it's an option.
  • C) Fetch the SRCINFO in AURCache, identify the keys in rust using alpm-srcinfo, then import them in the builder. At least no need for a bash loop and sed.
  • D) Fetch the SRCINFO in AURCache, identify the keys, and import them (if needed) in a keyring that AURCache owns, then bind-mount that keyring in the builder. Will need some special care to be kept up-to-date without requiring to run --populate every time we build a package.

If we move to remote builders, we might not be able to bind-mount from aurcache, and will need a "local" keyring on the builder. It'll need to list the keys from the SRCINFO and import them (in the chroot), which would end up quite similar to the current solution (or the replacement rust script).

@gyscos gyscos May 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll eventually move towards getting the SRCINFO in AURCache since that's already what we do for git packages, and it'll solve the platform-specific dependency issue listed in another comment. Solution C) from above.

If the builder is persistent it should be able to keep the keys from build to build without a bind-mount, and not pull them as much. It'll solve the problem of constantly pulling from the keyserver and getting rate-limited.

"pgp_keys=\"$(if [ -f .SRCINFO ]; then \
sed -n 's/^[[:space:]]*validpgpkeys[[:space:]]*=[[:space:]]*//p' .SRCINFO; \
else \
makepkg --printsrcinfo | sed -n 's/^[[:space:]]*validpgpkeys[[:space:]]*=[[:space:]]*//p'; \

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AUR packages should have a SRCINFO - for git repo, I think so too? Maybe we don't need the fallback?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its fine as is, SRCINFO is optional locally and is generated from the PKGBUILD (but not when uploading to AUR)
This would remove the simplicity of just adding a PKGBUILD to a git repo and add it to AURCache... (you'd add an additinal step)

@Lukas-Heiligenbrunner

Copy link
Copy Markdown
Owner

Thanks a lot for all the changes! I think I'll need some time to review all of this properly :)

@gyscos

gyscos commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

A small note: PKGBUILDS and SRCINFO can list platform-specific dependencies (for example depends_aarch64). An example such package is git-annex-standalone.

In that case, the AUR api returns the union of the dependencies of all platforms, and that's what we currently use to build the dependency graph. It means that if we satisfy our dependency graph, then we know the package can be built for all platforms. But it does mean we might try to build more than is strictly necessary. For example in this case we would build libffi7, even when only building git-annex-standalone for x86_64. (In practice this happens very rarely, it was not trivial to find a package doing that.)

A more precise but more complex alternative would have platforms per dependency link and only try to build what is actually needed for the required platform. To do that we'd also need to get the dependencies from the SRCINFO since the AUR api doesn't mention that. This means downloading the snapshot tarball to AURCache (we'd only extract the SRCINFO, but we'd still likely need to decompress the whole thing), which would be a larger change. Possible, but maybe later, as this PR is already large enough.

Copilot AI review requested due to automatic review settings May 16, 2026 12:24

This comment was marked as spam.

@Lukas-Heiligenbrunner Lukas-Heiligenbrunner left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, some points.
I accidently triggered this copilot thingie, sorry for that, no idea how i can delete it again


for (archive_path, parsed) in &build_pkgs {
let archive_name = archive_path.file_name().to_str().unwrap().to_string();
let platform = self.build_model.platform.get()?.clone();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be moved outside the loop?

archive_name
);
let existing = Files::find()
.filter(files::Column::Filename.eq(&archive_name))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we should probably filter for the corresponding platform?
.filter(files::Column::Platform.eq(&platform))

If platform=any two builds with the same filename may be produced?

for (pkg_file, file) in remaining_old_files {
let Some(file) = file else { continue };
let stale = Files::find()
.filter(files::Column::PackageId.eq(pkg_id))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

Comment thread backend/aurcache-deps/src/lib.rs Outdated
@@ -0,0 +1,322 @@
use std::collections::{HashMap, HashSet};

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please split the contents of this file into several rs files, or at least another one than lib.rs

Comment thread backend/aurcache-deps/src/lib.rs Outdated
/// Extract dependencies and sub-package names from a parsed .SRCINFO.
pub fn deps_from_srcinfo(source_info: &SourceInfoV1) -> PkgDeps {
let packages = source_info
.packages_for_architecture(alpm_types::SystemArchitecture::X86_64)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is x86_64 hardcoded, so always the amd64 deps are resolved. Shouldn't this be dependent on the build platform?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops indeed!

"sudo pacman -Syu --noconfirm --noprogressbar --color never && \
mkdir -p {build_dir} && cd {build_dir} && \
curl -sL '{snapshot_url}' | tar xz && \
cd {pkgbase} && \

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A malicious pkgbase variable could basically execute any command in the container. I'm not sure how severe this is, but if its just "/ && wget https://stuff.example"

@gyscos gyscos May 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A malicious package could already run arbitrary commands when we start building. We mostly want to avoid privilege escalation with sudo.

I agree a more robust way would be great, will check when I get back at a computer

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah true. I think its fine as is for now. We have other more important problems...

build_cmd: &str,
) -> String {
format!(
"cat <<'__AURCACHE_MAKEPKG_EOF__' > {makepkg_config_path}\n{makepkg_config}\n__AURCACHE_MAKEPKG_EOF__\n\

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One could also inject arbitrary commands here when colosing the heredoc inside the config file and && a second command...

I think in the long term when we have seperate aurcache-builder instances this can be easier solved when the builder spawns builds or an aurcache daemon runs inside the container and starts a build.

@gyscos gyscos May 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's user-controlled, right? If a user really wants to inject commands into their own environment...

I agree that a more robust way would be good. Moving to a proper communication protocol rather than bash commands would help.

We can also sanitize the value, for example through base64, then call base64 -d in the container. A bit inefficient but not terribly ugly.

Or in this case we might send this through env vars of the docker container, so we can easily access it from the bash process

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Yeah I'd leave it as is for now.
We should just keep in mind that this is not optimal ind the docker CMD length is byte limited at some point.

env var might be a good option tho, also for other parameters.

> 0;

if !has_pending_build {
package_update_with_client(client, db, dep_pkg.clone(), true, tx).await?;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the dependent platform is only triggered when the platform matches, right?
So when the parent has platform aarch64 and x86_64 and the child onlx x86_64 this probably won't work?
Or am i missing somehting?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also managed to have an infinite recursion which resulted on an stack overflow on this line.
I force updated a package which is already up-to-date and then, I think dependencies_ready_for_platform is called infinitly? I'm not sure tbh

@gyscos

gyscos commented May 16, 2026

Copy link
Copy Markdown
Contributor Author

Hi, some points. I accidently triggered this copilot thingie, sorry for that, no idea how i can delete it again

No problems, it's probably worth looking at, at least

@gyscos
gyscos force-pushed the feature/dependency-resolution branch from 9f69829 to 9245ebe Compare May 17, 2026 01:23
Comment thread docker/add-aur.sh
rm /etc/pacman.d/mirrorlist.backup

pacman --sync --needed --noconfirm --noprogressbar sudo base-devel git || echo "Nothing to do"
pacman --sync --needed --noconfirm --noprogressbar sudo base-devel multilib-devel git || echo "Nothing to do"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR, and not technically required to build aur packages, but many packages that were moved from multilib to AUR omit this dependency, which causes build errors for a bunch of lib32-... packages.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, we keep that in.

liblzma = { version = "0.4.1", features = ["parallel"] }
md5 = "0.8.0"
sha2 = "0.11.0"
xz2 = "0.1.7"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

liblzma is the maintained fork of xz2, which is what alpm-compress uses. The two are incompatible (because they both link to the C liblzma). Moving to liblzma was required to be able to use alpm-compress, but it's also probably a good idea regardless.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know about that, yes sounds good.

@@ -0,0 +1,1273 @@
use aurcache_db::migration::Migrator;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any test that uses mocks to actually run (local) network queries was considered "integration test" and moved to the tests folder, rather than inline unit tests. They're still much faster to run than the full e2e test.

Comment thread backend/aurcache-utils/src/pkg.rs Outdated
.or_insert_with(|| constraint.to_string());
}

fn split_constraints(constraint: &str) -> Vec<&str> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While alpm-types has some helpers to parse individual constraints, I couldn't find anything for comma-separated constraint bounds like >=1.0,<2.0. Maybe we could post an issue on their gitlab to add that.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes probably sense to file an issue.
Is a comma seperated constraint list a pacman standard used in pkgbuilds?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh maybe not indeed, could not find an example nor doc implying it's supported. Moving to better typed representation for our own internal case (merging different constraints from several split packages from the same base). Though even that is quite the edge case.

@gyscos
gyscos force-pushed the feature/dependency-resolution branch 9 times, most recently from 8875a13 to 52783fc Compare May 19, 2026 21:28
@gyscos
gyscos force-pushed the feature/dependency-resolution branch 2 times, most recently from 18e5fab to 4064da5 Compare May 20, 2026 17:00
Comment on lines -63 to -68
#[derive(Deserialize, ToSchema, Serialize, Default, Clone)]
pub struct GitPackage {
pub git_url: String,
pub git_ref: String,
pub subfolder: String,
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidated with the one from aurcache_db.

Comment thread backend/aurcache-api/src/package.rs Outdated
let update_pkg = packages::ActiveModel {
id: Set(id),
name: input.name.clone().map_or(NotSet, Set),
name: new_name.clone().map_or(NotSet, Set),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we even need the ability to change the package name, or the out of date status, or most of the other fields we are patching here?

If it's only meant to be used by the UI, then we don't need more than just patching the build flags.

) -> Result<Json<Vec<SimplePackageModel>>, NotFound<String>> {
let db = db as &DatabaseConnection;

list_directly_requested_packages(db, limit, page)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we add better UI filters, we can consider returning everything here and letting the UI show/hide the dependency-only packages. Or have filters in the query args, since we already do pagination here.

Ok(result)
}

async fn count_directly_requested_packages(db: &DatabaseConnection) -> anyhow::Result<u32> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too we could count separately the number of total vs directly requested packages

Comment on lines +364 to +379
let dependent_ids: Vec<i32> = Dependencies::find()
.filter(dependencies::Column::DependeeId.eq(pkg_id))
.select_only()
.column(dependencies::Column::DependentId)
.into_tuple()
.all(&self.db)
.await?;

if dependent_ids.is_empty() {
return Ok(HashMap::new());
}

let deps = Dependencies::find()
.filter(dependencies::Column::DependentId.is_in(dependent_ids))
.all(&self.db)
.await?;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be done in a single query with a self-join, but:

  • SeaORM apparently doesn't make that easy
  • Raw sql would be an option, but there might be some postgres vs sqlite differences

/// they can reach AURCache directly without port exposure on the host.
/// 3. **DinD mode**: inspect the `bridge` network gateway. Builder containers
/// created by the internal Docker daemon reach AURCache at the gateway IP.
pub async fn get_repo_config(docker: &bollard::Docker) -> anyhow::Result<&'static RepoConfig> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another ugly part, but hopefully it'll go away eventually.

To install packages in the builder, we need to add AURCache as a repo. Initially I was using a file:// repo by bind-mounting it into the builder, which works, but like all bind-mounting it needs special logic for DinD/Host.

Using http:// instead removes the need for the bind-mount and is more in line with a future plan of having remote workers. We just need to know AURCache's own address.

When we move to remove builders, the builder config will be a natural place for that. But for now we don't have that config, and it's surprisingly painful to get the current IP.

  • In DinD mode, we can talk to the gateway, which is usually a fixed IP but could be configured to something else. If the aurcache builder image is the only supported way to run AURCache then we could control that, but if it's "just" a convenient pre-built image to distribute, we could consider alternative images built around AURCache with different docker configs. In that case we can still find the gateway with some docker commands.
  • In Host mode, by default spawned containers cannot actually reach other containers, so we also need to spawn them in the same network. For that we need to get the current network + the current address, which we can also do from docker.

I don't like this method, but I think it isolates the mess in a piece that's relatively easy to remove if/when we move to remote/persistent builders.

@gyscos

gyscos commented May 22, 2026

Copy link
Copy Markdown
Contributor Author

I just realized there's a flaw in the current build queuing in this PR:

  • If A depends on B, a build for A can only be queued up if a build of B (with a good enough version) exists. This means that until the first build of B completes, A does not even have a build queued up at all.
  • When a build finishes, all dependents of that packages are checked. The ones that have all their dependencies satisfied have a build queued up.

The main advantage is that if a build is queued, it can always be started.

But it has a few drawbacks:

  • There's no indicator that a package is waiting for its dependencies to build.
  • Whenever a dependency gets an update, all dependent packages are rebuilt.
    • You could argue that's a feature, as some packages do need to be rebuilt when a dependency is updated (for example when protobuf or some haskell packages gets updated, all dependents should be rebuilt to link to the new .so).
    • But it's not the case for every package, and for protobuf and haskell, it doesn't even help since these are not AUR dependencies.

So I think I will instead directly queue up the build for A, but only start executing it when the dependencies are ready. Maybe add a state to the build queue like "waiting_for_deps". When a build completes, if any dependent has a build in this new state, and if all the dependencies of this dependent are satisfied, then we can update the build to a regular queued.
This way, there will be the indicator that a build is pending (just not ready yet), and it will only queue up dependent builds if there was one pending in the first place.

@gyscos
gyscos force-pushed the feature/dependency-resolution branch from 7971e5c to 6de2301 Compare June 1, 2026 15:03
Comment thread docker/builder.Dockerfile
FROM --platform=linux/arm/v7 lopsided/archlinux-arm32v7:latest AS arch_armv7

########## Target sysroot (extract libs for cross-compilation) ##########
FROM arch_${TARGETARCH}${TARGETVARIANT:+${TARGETVARIANT}} AS target_sysroot

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needing to build paru simplifies quite a bit.

Comment thread docker/add-aur.sh
echo "${AUR_USER} ALL=(ALL) NOPASSWD: /usr/bin/pacman" > "/etc/sudoers.d/allow_${AUR_USER}_to_pacman"
echo "${AUR_USER} ALL=(ALL) NOPASSWD: /usr/bin/pacman-key" >> "/etc/sudoers.d/allow_${AUR_USER}_to_pacman"
echo "${AUR_USER} ALL=(ALL) NOPASSWD: /usr/bin/chmod" >> "/etc/sudoers.d/allow_${AUR_USER}_to_pacman"
echo "${AUR_USER} ALL=(ALL) NOPASSWD: /usr/bin/tee /etc/pacman.conf" >> "/etc/sudoers.d/allow_${AUR_USER}_to_pacman"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used to prepare the pacman.conf.

Alternatives:

  • Run the builder command as root, no need for sudo.
  • Bind-mount the pacman.conf file read-only, no need to prepare that from the builder. Con: confuses pacman's disk-space check when it tries to update pacman itself.

Will likely go away when/if we move to persistent builders.

use anyhow::{anyhow, bail};
use flate2::Compression;
use flate2::read::GzEncoder;
use flate2::write::GzEncoder;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a very confusing bug! flate2::read::GzEncoder does implement Write but just forwards the data uncompressed, it only compresses data that is read. This was used to write the archive, which resulted in an uncompressed archive, despite the .gz extension.

Some tools ignore the .gz extension and work transparently with uncompressed repos, but some don't (in particular the alpm-* crates).

let pkg_models: Vec<packages::Model> = Packages::find()
.filter(packages::Column::OutOfDate.eq(1))
.all(&txn)
.all(db)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transaction was only used for the initial select, it was not actually keeping the updates further down in the same transaction. As a result it wasn't really doing anything useful.

}

/// Fetch the dependency lists for a pkgbase via the AUR RPC.
pub async fn deps_of(&self, pkgbase: &str) -> Result<PkgDeps, Error> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I was reworking this part to be less clunky I found out a big issue with the current design: we cannot search the AUR rpc api by pkgbase, only by child pkg name. Many pkgbase have at least one child package with the same name as the base, but not all. For example https://aur.archlinux.org/pkgbase/czkawka defines czkawka-gui and czkawka-cli`.

So I'm moving away from querying the RPC to list dependencies, and instead will fetch the snapshot of the pkgbase and inspect the SRCINFO there. It will unify the git and aur source types (and in the future the upload type as well), and it's something I was thinking of doing anyway (though I hoped it could wait until later).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further AUR/git unification: we now checkout AUR packages by their git endpoint, and persist these checkouts (just like what paru does), which as a minor bonus makes the next update faster (not that snapshot download was a very slow point).

@Lukas-Heiligenbrunner

Copy link
Copy Markdown
Owner

Hi @gyscos I won't have time until July to work on this since its pretty stressful with work+uni currently. I'll review it then and give feedback (or you bring it to a RTM state before and I'm happy to merge it ;) )

@gyscos

gyscos commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Of course, no rush, it still needs quite some work before it's ready.

First time I try to use copilot to drive a new feature, I should have checked and cleaned up more frequently, now it's taking ages to go through everything. But will get there!

@gyscos
gyscos force-pushed the feature/dependency-resolution branch 2 times, most recently from 1256be2 to 5776ef3 Compare June 15, 2026 19:08
@gyscos
gyscos force-pushed the feature/dependency-resolution branch from 5776ef3 to 34304ed Compare June 15, 2026 20:39
// from looping: the AUR-reported version is the one from when the
// PKGBUILD was last touched, which may be *older* than what was
// actually built from the live VCS source.
let is_outdated = match &latest_version {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly speaking required by this PR as it was existing before, but since this PR now updates the DB version based on the newly built package, this was happening much more often.

# Conflicts:
#	backend/Cargo.lock
#	backend/Cargo.toml
#	backend/aurcache-builder/src/docker.rs
#	backend/aurcache-db/Cargo.toml
#	backend/aurcache-scheduler/Cargo.toml
@gyscos

gyscos commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

One note, might be coming in a follow-up PR: it would be nice to be able to manually edit the dependency tree of a package. When several packages "provide" the same required virtual dependency, we pick one automatically. But in some cases it makes sense not use a specific one. (In my use-case, steam-native-runtime ended up depending on jpegli-git to satisfy the libjpeg dependency, but I'd like to manually change that).

That feature may also help "replace" dependencies with git-packaged ones: if a AUR package happens to be broken, we can publish a git package instead (or one day an uploaded PKGBUILD). It'll be used as a dependency node for newly added packages, but it's not currently possible to "patch"/"update" an existing entry in the aurcache to point to that.

(We can also dream a UI mode where there's a "fork" button on a package page that opens an in-browser editor to patch a PKGBUILD or other source, but that's for another day.)

@Lukas-Heiligenbrunner

Lukas-Heiligenbrunner commented Jul 13, 2026 via email

Copy link
Copy Markdown
Owner

@gyscos
gyscos force-pushed the feature/dependency-resolution branch from 72ada6a to 2a2ea2c Compare August 12, 2026 16:45
@gyscos

gyscos commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Minor details that can also be fixed later: this PR adds a few UI elements:

  • On a package page, the right panel now includes a clickable list of dependencies/dependent packages, drawn as "pills", but slightly less rounded than the non-clickable options below. We could make it more similar to the options, or different entirely.
image

The package and build list view now has a new icon for a build that's waiting for dependencies to be ready. I used the pause icon for that, and picked a different icon for a build that's in progress. We can revisit the icons and colors used - maybe the clock face is better to say it's waiting, and another icon could indicate the build is in progress.

image

In the future, more UI changes could come:

  • Provide a separate package count for explicitly requested vs total (including dependencies)
  • In the list of dependencies, add an icon for the status of that package, so it's easy to find what dependency a build/package is still waiting for.

Dependency resolution moves out of `paru` running inside the builder and
into AURCache itself, so the server knows the package graph rather than
discovering it mid-build.

Resolution and sources:

* `aurcache-db::helpers::dependency_resolution` resolves a dependency
  name to its source — an already-tracked local package (by name, split
  package or `provides`), an official repo package, or an AUR package —
  with local matches taking precedence.
* Official-repo membership is answered from downloaded repo databases
  rather than an HTTP existence check, which could not see `provides`.
* AUR and git sources are fetched through one persistent git checkout
  cache, so a re-resolve is an incremental fetch rather than a re-clone,
  and one `SnapshotStore` instance is shared by the build queue, the
  schedulers and auto-update instead of each keeping its own.
* Requesting a rebuild at a new version updates the dependency graph,
  which previously kept the constraints recorded at first add.

Fixes to out-of-date detection, which the above depends on:

* A cached checkout never advanced past its first clone. `fetch` only
  moves `refs/remotes/origin/*`, so resolving the ref locally returned
  the originally cloned commit forever — every AUR package, since they
  resolve `HEAD`. Out-of-date detection (live AUR RPC) then reported an
  update while the build read the frozen pkgver and refused it with
  "Latest build is already up to date". Resolve against the fetched
  remote-tracking ref and keep HEAD on the branch so it follows upstream;
  tags and pinned SHAs still detach. A force-pushed branch resets onto
  the rewritten history, which suits a disposable source cache.
* One failing git package aborted the whole periodic version check, so
  every package after it kept a stale `out_of_date` flag until a later
  run — and that run would abort in the same place. Failures are now
  isolated per package, matching the AUR branch.
* Repo database downloads were transparently gunzipped by reqwest, so
  the `.db.tar.gz` written to the cache was not what the parser expected.

Squashed from the branch's development history; the checkout and
version-check fixes were developed on feature/remote-worker and
backported here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrGKN5md6Vypip4FEw5X1Q
@gyscos
gyscos force-pushed the feature/dependency-resolution branch from a3f1432 to c3517c3 Compare August 24, 2026 18:37
gyscos and others added 3 commits August 26, 2026 10:40
`multi_info_of` put one `arg[]` per package into a single GET. The server
rejects a request line over 8 KiB with `414 Request-URI Too Large` — measured
against the real service, 8185 bytes answers and 8313 does not — which at
roughly 25 bytes per package caps one request near 300 packages.

The failure was not partial: `check_versions` propagates the error, so one
oversized request stopped *every* package from being version-checked, on every
pass, for as long as the package count stayed above the limit. The only
symptom was a single log line, and the retry policy retried a 414 with backoff
even though it is never transient.

Queries are now split to stay under the limit and the results concatenated,
measured against the encoded URL rather than a package count: the 187 AUR
packages containing `+` are four bytes longer than they look.

`resolve_bases` is chunked too — it is handed a whole dependency list.

An individual chunk coming back empty is no longer an error: those packages
are simply not in the AUR any more.

Back-ported from feature/pkgbase-url, adapted for this branch's `Error`, which
maps `url::ParseError` explicitly rather than via `From`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrGKN5md6Vypip4FEw5X1Q
`deps_from_srcinfo` read `.SRCINFO` for `SystemArchitecture::X86_64`,
hardcoded — the only call site — while packages carry a platform list and
builds run per platform. A PKGBUILD can declare `depends_aarch64` separately
from `depends`, so a package built only for aarch64 had its dependency graph
computed from an architecture it is never built on: missing what it needs, and
requiring what it does not.

It now takes the architectures and returns the union across them. The graph is
stored once per package rather than once per platform, so it has to cover
every platform the package is built for. That over-requires where two
architectures need different things, which is the safe direction — a
dependency that is built but unused costs a build, where a missing one breaks
the package.

An empty platform list falls back to x86_64 rather than resolving to no
dependencies at all: no configured platforms is missing configuration, not a
package that needs nothing.

Back-ported from feature/pkgbase-url. Only the resolution half came across:
there, changing a package's platform set also re-runs the resolution, which
needs `package_resync_dependencies` — a function this branch does not have. So
a platform set edited after the fact still leaves the stored graph stale here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrGKN5md6Vypip4FEw5X1Q
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants