Dependency resolver
Resolve-BcDependencies is ALbuild's headline capability: a real transitive dependency resolver across package feeds that respects the actual Business Central build and the cross-dependencies of candidate packages.
Why it matters
Unlike a greedy "latest that satisfies the minimum version" approach, ALbuild solves for a mutually-compatible, transitively-complete set whose every member's own dependencies, including Microsoft first-party apps, are satisfied both by the chosen set and by the target build. The result is captured in a dependencies.lock.json for reproducible builds.
The problem it solves
Given the app(s) being built and the BC build they target, produce a mutually-compatible, transitively-complete set of dependency packages. For on-premise targets it selects the runtime package whose platform version matches the target build.
Get-BcContainerAppInfo feeds the
separate -InstalledApps parameter: the apps a build container already has, pinned as a satisfied
baseline so they are not fetched again.
`app.json` is a floor, not a ceiling
application and platform in app.json are your app's minimum supported versions. Using them
as the resolution ceiling is a subtle and expensive mistake: the resolver then rejects real
dependency apps that were built for the current BC and silently falls back to symbol-only
AppSource packages, which compile but cannot be installed.
The ceiling is therefore chosen separately, by
Resolve-BcDependencyCeiling, highest
precedence first:
- An explicit target version. In the Resolve Dependencies task this defaults to
$(bcArtifactVersion)from Get BC Artifact, so even a container-less build honours the BC version it targets. - The platform app installed in the build container, which is the exact build.
bcVersionpinned inalbuild.json.
If none is available, the resolver falls back to the newest version each app.json is compatible
with.
Feed providers
Feeds can be supplied three ways: passed via -Feeds, registered with Register-BcFeed, or, most conveniently, declared once in the project's albuild.json feeds array, which Resolve-BcDependencies loads automatically (see project config). Because the resolver detects runtime/indirect packages from metadata and finds packages by app id, a declared feed is usually just its URL.
Supported sources: NuGet v3 (nuget.org, Azure DevOps, GitHub Packages), Azure Universal Packages, GitHub Releases, the local workspace (.alpackages), and the Microsoft public feeds (MSSymbols, MSApps, AppSourceSymbols). Each provider maps an AL app id + publisher + name to a feed package id according to the package kind:
| Kind | Package id scheme |
|---|---|
| symbols | {publisher}.{name}.symbols.{id} |
| apps | {publisher}.{name}.{id} |
| runtime | {publisher}.{name}.runtime-{version} |
| localized | …{countryCode} |
The empty GUID 00000000-0000-0000-0000-000000000000 denotes Platform/Application and is provided by the container, never downloaded. Microsoft apps are pinned to the target build and never downloaded from a third-party feed, recognised by a Microsoft publisher, a Microsoft. package-id prefix, or the empty GUID. This covers the first-party apps System Application (63ca2fa4-…), Base Application (437dbf0e-…) and Business Foundation (f3552374-…), as well as platform-shipped extensions an ISV app may depend on.
Real-world NuGet conventions consumed
ISVs publish to NuGet with Microsoft's BC packaging conventions, which the provider reads directly:
- Dependencies come from the package's NuGet metadata (the nuspec
<dependency>entries), not from the.app'sNavxManifest.xml. This is read over the network without downloading the.app, and is the only thing that works for runtime apps.Microsoft.Application/Microsoft.Platformdependencies carry the required BC version. - Direct app packages (e.g. Continia, Simova) contain the
.app/.runtime.appthemselves. - Indirect "metapackages" (e.g. DYCE, 365 business development) contain no
.app; their only payload dependency is a per-app-version runtime sub-package, whose own NuGet versions are the BC platform builds it was compiled for. For an on-premise target the provider selects the build closest to but not exceeding the target platform (e.g. BC27.1.41698.51164→27.1.41698.41776) and downloads that sub-package, which also carries the localised runtime app. - The app id (trailing GUID) is the only stable key. Publishers vary the publisher/name segments. The provider therefore (1) carries the exact nuspec package id as a hint, (2) rewrites a plain
{publisher}.{name}.{id}hint to this feed's id scheme infix, and (3) falls back to the feed's search service queried by app id: picking the package whose id ends in that GUID. Results are memoised so the backtracking solver does not re-fetch them.
Source precedence
Beyond feeds, a project can supply dependencies as committed local .app files (localPackages), direct-download URLs (urlPackages) and Azure DevOps Universal packages (universalPackages) — see dependency sources. When the same app is available from more than one source, the resolution order is:
localPackages/urlPackages— authoritative. A dependency you ship directly (a committed.appor an ISV's download link) is pinned and always used, even when a registered feed offers a newer version. This keeps an ISV app that has no public feed — or one you deliberately fix to a known build — from being replaced by a feed copy.- NuGet feeds.
universalPackages— feed-subordinate. A registered feed's version wins over a universal copy; the universal package stays only as a fallback for apps no feed provides.
Name-based pinning (encrypted runtime apps)
A local/URL .app is normally read to get its app id and pinned by id. An encrypted BC runtime package cannot be read on the host, so instead its {Publisher}_{Name}_{Version}.app file name is parsed and the dependency is pinned by Publisher + Name (exact, case-insensitive). Such a package:
- is never fetched from a feed (the declared dependency counts as satisfied locally), and is installed straight from
localPackages— its real id/version are read inside the container; - is copied into
.alpackageslike every other local package, and both compile engines resolve against it from there — the host AL Tool as well as the container compiler.alcreads the runtime package's symbol reference itself, so a separate symbol.appis not required.
The name-based pinning therefore exists because ALbuild's host-side reader cannot open the package, not because the compiler cannot use it. When a build container is available, ALbuild reads the real app id through it (the container decrypts the manifest) and pins by id instead.
Choosing the BC version: downgrade on conflict
There is a chicken-and-egg problem in a Select = Latest pipeline: you want the newest Business
Central build, but your ISV dependencies may not have shipped packages for it yet. Picking the
latest artifact and then discovering that dependencies do not resolve wastes a container and
fails the build.
Select-BcArtifactForDependencies
inverts that. It enumerates the latest artifact and up to -MaxStepsBack earlier minor versions
(crossing a major boundary to the previous major's newest minor when a minor reaches x.0) and,
newest first, dry-runs the resolver (-SkipDownload) for every project against that build. The
first version where all projects resolve is the one used.
- If that is older than the latest, the result is flagged
DowngradedandConflictReasonexplains why the latest was skipped, which the pipeline surfaces as a warning rather than swallowing it. - Because it uses an explicit target build and a synthetic Microsoft baseline, no container is needed: the decision is made before the single container is created.
- An explicit
-Versionpins the choice and skips the enumeration entirely.
Diagnosing a resolve without changing anything
Get-BcDependencyReconciliation is
read-only: it installs nothing. For each dependency in app.json it reports whether the dependency
is already satisfied by an app installed in the container, otherwise whether a configured feed
can provide a compatible version, otherwise that it is missing. Microsoft first-party apps
ship with the artifact, so they normally show up as satisfied by the container.
Use it when a resolve fails and you need to know which side the gap is on, before touching feeds or versions.
The algorithm (backtracking constraint solve)
- Pin Microsoft apps to the target build (discovered transitively, not just the three first-party apps); a target older than a Microsoft requirement is a precise, reported conflict.
- Pick candidate versions (≥ minimum) across feeds for each unresolved dependency, filtered to those whose
platform/applicationare compatible with the target build. For an indirect metapackage this also requires that its runtime sub-package has a build at or below the target. - Assign and recurse: assign a candidate, enqueue its transitive dependencies, and recurse; on conflict, backtrack.
- Write the result: on success, write the deduplicated, topologically sorted set (dependencies before dependants) to
.alpackagesand adependencies.lock.jsonfor reproducible builds, recording, for each package, both the app version and the concrete download package/version actually fetched. On failure, emit a precise conflict report.
Version-range checks reuse Test-BcVersionInRange / ConvertTo-BcVersion from the Core module.
Lock file
The dependencies.lock.json records, for every package, both the app version and the concrete downloadPackage / downloadVersion actually fetched (these differ from the app package for indirect metapackages). Commit it to get reproducible builds: the same dependency set every time, regardless of what newer versions later appear on a feed.
Related cmdlets
Resolve-BcDependencies: the resolver.Register-BcFeed/Get-BcFeed, feed registration.Find-BcPackage: query a feed for candidates.Get-BcUniversalPackage: Azure DevOps Universal feed downloads.Resolve-BcExternalDependency: local committed.appsources.Resolve-BcDependencyCeiling: pick the BC version to cap resolution against.Select-BcArtifactForDependencies: dependency-aware artifact selection with downgrade-on-conflict.Get-BcDependencyReconciliation: read-only report of satisfied / available / missing dependencies.


