-
Notifications
You must be signed in to change notification settings - Fork 6
feat(projectconfig): add package publish channel annotations to TOML schema #38
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
base: main
Are you sure you want to change the base?
Changes from all commits
58fcdc0
3de812a
88bb6d6
3fd9797
e420ee6
201f05b
cb54100
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| # Package Groups | ||
|
|
||
| Package groups let you apply shared configuration to named sets of binary packages. They are defined under `[package-groups.<name>]` in the TOML configuration. | ||
|
|
||
| Package groups are evaluated at build time, after the binary RPMs are produced. They are analogous to [component groups](component-groups.md), which apply shared configuration to sets of components. | ||
|
|
||
| ## Field Reference | ||
|
|
||
| | Field | TOML Key | Type | Required | Description | | ||
| |-------|----------|------|----------|-------------| | ||
| | Description | `description` | string | No | Human-readable description of this group | | ||
| | Packages | `packages` | string array | No | Explicit list of binary package names that belong to this group | | ||
| | Default package config | `default-package-config` | [PackageConfig](#package-config) | No | Configuration inherited by all packages listed in this group | | ||
|
|
||
| ## Packages | ||
|
|
||
| The `packages` field is an explicit list of binary package names (as they appear in the RPM `Name` tag) that belong to this group. Membership is determined by exact name match — no glob patterns or wildcards are supported. | ||
|
|
||
| ```toml | ||
| [package-groups.devel-packages] | ||
| description = "Development subpackages" | ||
| packages = ["libcurl-devel", "curl-static", "wget2-devel"] | ||
|
|
||
| [package-groups.debug-packages] | ||
| description = "Debug info and source packages" | ||
| packages = ["curl-debuginfo", "curl-debugsource", "wget2-debuginfo"] | ||
| ``` | ||
|
|
||
| > **Note:** A package name may appear in at most one group. Listing the same name in two groups produces a validation error. | ||
|
|
||
| ## Package Config | ||
|
|
||
| The `[package-groups.<name>.default-package-config]` section defines the configuration applied to all packages matching this group. | ||
|
|
||
| ### PackageConfig Fields | ||
|
|
||
| | Field | TOML Key | Type | Required | Description | | ||
| |-------|----------|------|----------|-------------| | ||
|
|
||
| | Publish settings | `publish` | [PublishConfig](#publish-config) | No | Publishing settings for matched packages | | ||
|
|
||
| ### Publish Config | ||
|
|
||
| | Field | TOML Key | Type | Required | Description | | ||
| |-------|----------|------|----------|-------------| | ||
| | Channel | `channel` | string | No | Publish channel for this package. Use `"none"` to signal to downstream tooling that this package should not be published. | | ||
|
|
||
| ## Resolution Order | ||
|
|
||
| When determining the effective config for a binary package, azldev applies config layers in this order — later layers override earlier ones: | ||
|
|
||
| 1. **Project `default-package-config`** — lowest priority; applies to all packages in the project | ||
| 2. **Package group** — the group (if any) whose `packages` list contains the package name | ||
| 3. **Component `default-package-config`** — applies to all packages produced by that component | ||
| 4. **Component `packages.<name>`** — highest priority; exact per-package override | ||
|
|
||
| > **Note:** Each package name may appear in at most one group. Listing the same name in two groups produces a validation error. | ||
|
|
||
| ## Example | ||
|
|
||
| ```toml | ||
| # Set a project-wide default channel | ||
| [default-package-config.publish] | ||
| channel = "rpm-base" | ||
|
|
||
| [package-groups.devel-packages] | ||
| description = "Development subpackages" | ||
| packages = ["libcurl-devel", "curl-static", "wget2-devel"] | ||
|
|
||
| [package-groups.devel-packages.default-package-config.publish] | ||
| channel = "rpm-build-only" | ||
|
|
||
| [package-groups.debug-packages] | ||
| description = "Debug info and source" | ||
| packages = [ | ||
| "libcurl-debuginfo", | ||
| "libcurl-minimal-debuginfo", | ||
| "curl-debugsource", | ||
| "wget2-debuginfo", | ||
| "wget2-debugsource", | ||
| "wget2-libs-debuginfo" | ||
| ] | ||
|
|
||
| [package-groups.debug-packages.default-package-config.publish] | ||
| channel = "rpm-debug" | ||
| ``` | ||
|
|
||
| ## Related Resources | ||
|
|
||
| - [Project Configuration](project.md) — top-level `default-package-config` and `package-groups` fields | ||
| - [Components](components.md) — per-component `default-package-config` and `packages` overrides | ||
| - [Configuration System](../../explanation/config-system.md) — inheritance and merge behavior |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,12 +8,15 @@ import ( | |
| "fmt" | ||
| "path/filepath" | ||
|
|
||
| rpmlib "github.com/cavaliergopher/rpm" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/componentbuilder" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/components" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/sources" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/workdir" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/buildenv" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/global/opctx" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/providers/sourceproviders" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/utils/defers" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/utils/fileutils" | ||
|
|
@@ -37,6 +40,20 @@ type ComponentBuildOptions struct { | |
| MockConfigOpts map[string]string | ||
| } | ||
|
|
||
| // RPMResult encapsulates a single binary RPM produced by a component build, | ||
| // together with the resolved publish channel for that package. | ||
| type RPMResult struct { | ||
| // Path is the absolute path to the RPM file. | ||
| Path string `json:"path" table:"Path"` | ||
|
|
||
| // PackageName is the binary package name extracted from the RPM header tag (e.g., "libcurl-devel"). | ||
| PackageName string `json:"packageName" table:"Package"` | ||
|
|
||
| // Channel is the resolved publish channel from project config. | ||
| // Empty when no channel is configured for this package. | ||
| Channel string `json:"channel" table:"Channel"` | ||
| } | ||
liunan-ms marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // ComponentBuildResults summarizes the results of building a single component. | ||
| type ComponentBuildResults struct { | ||
| // Names of the component that was built. | ||
|
|
@@ -47,6 +64,13 @@ type ComponentBuildResults struct { | |
|
|
||
| // Absolute paths to any RPMs built by the operation. | ||
| RPMPaths []string `json:"rpmPaths" table:"RPM Paths"` | ||
|
|
||
| // RPMChannels holds the resolved publish channel for each RPM, parallel to [RPMPaths]. | ||
| // Empty string means no channel was configured for that package. | ||
| RPMChannels []string `json:"rpmChannels" table:"Channels"` | ||
|
|
||
| // RPMs contains enriched per-RPM information including the resolved publish channel. | ||
| RPMs []RPMResult `json:"rpms" table:"-"` | ||
| } | ||
|
|
||
| func buildOnAppInit(_ *azldev.App, parent *cobra.Command) { | ||
|
|
@@ -288,6 +312,18 @@ func buildComponentUsingBuilder( | |
| return results, fmt.Errorf("failed to build RPM for %q: %w", component.GetName(), err) | ||
| } | ||
|
|
||
| // Enrich each RPM with its binary package name and resolved publish channel. | ||
| results.RPMs, err = resolveRPMResults(env.FS(), results.RPMPaths, env.Config(), component.GetConfig()) | ||
| if err != nil { | ||
| return results, fmt.Errorf("failed to resolve publish channels for %q:\n%w", component.GetName(), err) | ||
| } | ||
|
Comment on lines
+315
to
+319
|
||
|
|
||
| // Populate the parallel Channels slice for table display. | ||
| results.RPMChannels = make([]string, len(results.RPMs)) | ||
| for rpmIdx, rpm := range results.RPMs { | ||
| results.RPMChannels[rpmIdx] = rpm.Channel | ||
| } | ||
|
|
||
| // Publish built RPMs to local repo with publish enabled. | ||
| if localRepoWithPublishPath != "" && len(results.RPMPaths) > 0 { | ||
| publishErr := publishToLocalRepo(env, results.RPMPaths, localRepoWithPublishPath) | ||
|
|
@@ -352,6 +388,59 @@ func checkLocalRepoPathOverlap(localRepoPaths []string, localRepoWithPublishPath | |
| return nil | ||
| } | ||
|
|
||
| // resolveRPMResults builds an [RPMResult] for each RPM path, extracting the binary package | ||
| // name from the RPM headers and resolving its publish channel from the project config (if available). | ||
| // When no project config is loaded, the Channel field is left empty. | ||
| func resolveRPMResults( | ||
| fs opctx.FS, rpmPaths []string, proj *projectconfig.ProjectConfig, compConfig *projectconfig.ComponentConfig, | ||
| ) ([]RPMResult, error) { | ||
| rpmResults := make([]RPMResult, 0, len(rpmPaths)) | ||
|
|
||
| for _, rpmPath := range rpmPaths { | ||
| pkgName, err := packageNameFromRPM(fs, rpmPath) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to determine package name:\n%w", err) | ||
| } | ||
|
|
||
| rpmResult := RPMResult{ | ||
| Path: rpmPath, | ||
| PackageName: pkgName, | ||
| } | ||
|
|
||
| if proj != nil { | ||
| pkgConfig, err := projectconfig.ResolvePackageConfig(pkgName, compConfig, proj) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We may have already discussed this, but what do you think about (in a separate PR) exposing a command-line verb to resolve the configuration for a named package?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a reasonable idea. The infrastructure (ResolvePackageConfig) is already in place and does exactly the right thing. A separate PR will add |
||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to resolve package config for %#q:\n%w", pkgName, err) | ||
| } | ||
|
|
||
| rpmResult.Channel = pkgConfig.Publish.Channel | ||
| } | ||
|
|
||
| rpmResults = append(rpmResults, rpmResult) | ||
| } | ||
|
|
||
| return rpmResults, nil | ||
| } | ||
|
|
||
| // packageNameFromRPM extracts the binary package name from an RPM file by reading | ||
| // its headers. Reading the Name tag directly from the RPM metadata is authoritative and | ||
| // handles all valid package names regardless of naming conventions. | ||
| func packageNameFromRPM(fs opctx.FS, rpmPath string) (string, error) { | ||
| rpmFile, err := fs.Open(rpmPath) | ||
| if err != nil { | ||
| return "", fmt.Errorf("failed to open RPM %#q:\n%w", rpmPath, err) | ||
| } | ||
|
|
||
| defer rpmFile.Close() | ||
liunan-ms marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| pkg, err := rpmlib.Read(rpmFile) | ||
| if err != nil { | ||
| return "", fmt.Errorf("failed to read RPM headers from %#q:\n%w", rpmPath, err) | ||
| } | ||
|
|
||
| return pkg.Name(), nil | ||
| } | ||
|
|
||
| // publishToLocalRepo publishes the given RPMs to the specified local repo. | ||
| func publishToLocalRepo(env *azldev.Env, rpmPaths []string, repoPath string) error { | ||
| publisher, err := localrepo.NewPublisher(env, repoPath, false) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The field reference table is for the
[project]section, butdefault-package-configandpackage-groupsare top-level keys (seeConfigFilestruct tags / examples in this same doc). Listing them under[project]is misleading; move them to the appropriate top-level config documentation or clarify they are not nested under[project].