diff --git a/artifacts.go b/artifacts.go index b0c8039cf..f19b8677a 100644 --- a/artifacts.go +++ b/artifacts.go @@ -37,6 +37,10 @@ type Artifacts struct { // the [ImageConfig]. Links []ArtifactSymlinkConfig `yaml:"links,omitempty" json:"links,omitempty"` + // Headers is a list of header files and/or folders to be installed. + // On linux this would typically be installed to /usr/include/. + Headers map[string]ArtifactConfig `yaml:"headers,omitempty" json:"headers,omitempty"` + // TODO: other types of artifacts (libexec, etc) } @@ -135,5 +139,8 @@ func (a *Artifacts) IsEmpty() bool { if len(a.Links) > 0 { return false } + if len(a.Headers) > 0 { + return false + } return true } diff --git a/docs/spec.schema.json b/docs/spec.schema.json index 4fa9bb53b..6604bb613 100644 --- a/docs/spec.schema.json +++ b/docs/spec.schema.json @@ -132,6 +132,13 @@ }, "type": "array", "description": "Links is the list of symlinks to be installed with the package\nLinks should only be used if the *package* should contain the link.\nFor making a container compatible with another image, use [PostInstall] in\nthe [ImageConfig]." + }, + "headers": { + "additionalProperties": { + "$ref": "#/$defs/ArtifactConfig" + }, + "type": "object", + "description": "Headers is a list of header files and/or folders to be installed.\nOn linux this would typically be installed to /usr/include/." } }, "additionalProperties": false, diff --git a/frontend/deb/debroot.go b/frontend/deb/debroot.go index a18fb8aa4..483998862 100644 --- a/frontend/deb/debroot.go +++ b/frontend/deb/debroot.go @@ -388,6 +388,15 @@ func createInstallScripts(worker llb.State, spec *dalec.Spec, dir string) []llb. } } + if len(spec.Artifacts.Headers) > 0 { + sorted := dalec.SortMapKeys(spec.Artifacts.Headers) + for _, key := range sorted { + cfg := spec.Artifacts.Headers[key] + resolved := cfg.ResolveName(key) + writeInstall(key, filepath.Join("/usr/include", cfg.SubPath), resolved) + } + } + if units := spec.Artifacts.Systemd.GetUnits(); len(units) > 0 { // deb-systemd will look for service files in DEBIAN/[.]. // To handle this we'll create symlinks to the actual unit files in the source. diff --git a/frontend/rpm/template.go b/frontend/rpm/template.go index ea930fbfa..a446308d5 100644 --- a/frontend/rpm/template.go +++ b/frontend/rpm/template.go @@ -546,6 +546,12 @@ func (w *specWrapper) Install() fmt.Stringer { fmt.Fprintln(b, "ln -sf", l.Source, "%{buildroot}/"+l.Dest) } + headersKeys := dalec.SortMapKeys(w.Spec.Artifacts.Headers) + for _, h := range headersKeys { + cfg := w.Spec.Artifacts.Headers[h] + copyArtifact(`%{buildroot}/%{_includedir}`, h, &cfg) + } + b.WriteString("\n") return b } @@ -673,6 +679,15 @@ func (w *specWrapper) Files() fmt.Stringer { fmt.Fprintln(b, l.Dest) } + if len(w.Spec.Artifacts.Headers) > 0 { + headersKeys := dalec.SortMapKeys(w.Spec.Artifacts.Headers) + for _, h := range headersKeys { + hf := w.Spec.Artifacts.Headers[h] + path := filepath.Join(`%{_includedir}`, hf.SubPath, hf.ResolveName(h)) + fmt.Fprintln(b, path) + } + } + b.WriteString("\n") return b } diff --git a/frontend/rpm/template_test.go b/frontend/rpm/template_test.go index 106ef65f6..db76a2105 100644 --- a/frontend/rpm/template_test.go +++ b/frontend/rpm/template_test.go @@ -369,6 +369,47 @@ fi want := `%files %license %{_licensedir}/test-pkg/licenses/LICENSE.md +` + assert.Equal(t, want, got) + }) + + t.Run("test headers templating using defaults", func(t *testing.T) { + t.Parallel() + w := &specWrapper{Spec: &dalec.Spec{ + Name: "test-pkg", + Artifacts: dalec.Artifacts{ + Headers: map[string]dalec.ArtifactConfig{ + "test-headers": {}, + }, + }, + }} + + got := w.Files().String() + want := `%files +%{_includedir}/test-headers + +` + assert.Equal(t, want, got) + }) + + t.Run("test headers templating using ArtifactConfig", func(t *testing.T) { + t.Parallel() + w := &specWrapper{Spec: &dalec.Spec{ + Name: "test-pkg", + Artifacts: dalec.Artifacts{ + Headers: map[string]dalec.ArtifactConfig{ + "test-headers": { + Name: "sub-module-headers", + SubPath: "sub-module", + }, + }, + }, + }} + + got := w.Files().String() + want := `%files +%{_includedir}/sub-module/sub-module-headers + ` assert.Equal(t, want, got) }) diff --git a/test/azlinux_test.go b/test/azlinux_test.go index 5b98bb2c0..a88cb9c28 100644 --- a/test/azlinux_test.go +++ b/test/azlinux_test.go @@ -1059,7 +1059,7 @@ Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/boot }) }) - t.Run("docs and licenses are handled correctly", func(t *testing.T) { + t.Run("docs and headers and licenses are handled correctly", func(t *testing.T) { t.Parallel() spec := &dalec.Spec{ Name: "test-docs-handled", @@ -1103,6 +1103,30 @@ Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/boot }, }, }, + "src5": { + Inline: &dalec.SourceInline{ + Dir: &dalec.SourceInlineDir{ + Files: map[string]*dalec.SourceInlineFile{ + "header.h": { + Contents: "message=hello", + Permissions: 0o644, + }, + }, + }, + }, + }, + "src6": { + Inline: &dalec.SourceInline{ + Dir: &dalec.SourceInlineDir{ + Files: map[string]*dalec.SourceInlineFile{ + "header.h": { + Contents: "message=hello", + Permissions: 0o644, + }, + }, + }, + }, + }, }, Artifacts: dalec.Artifacts{ Docs: map[string]dalec.ArtifactConfig{ @@ -1117,15 +1141,37 @@ Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/boot SubPath: "license-subpath", }, }, + Headers: map[string]dalec.ArtifactConfig{ + // Files with and without ArtifactConfig + "src1": { + Name: "renamed-src1", + SubPath: "header-subpath-src1", + }, + "src2": {}, + // Directories with and without ArtifactConfig + "src5": { + Name: "renamed-src5", + SubPath: "header-subpath-src5", + }, + "src6": {}, + }, }, Tests: []*dalec.TestSpec{ { - Name: "Doc files should be created in correct place", + Name: "Doc, lib and header files should be created in correct place", Files: map[string]dalec.FileCheckOutput{ "/usr/share/doc/test-docs-handled/src1": {}, "/usr/share/doc/test-docs-handled/subpath/src2": {}, filepath.Join(testConfig.LicenseDir, "test-docs-handled/src3"): {}, filepath.Join(testConfig.LicenseDir, "test-docs-handled/license-subpath/src4"): {}, + "/usr/include/header-subpath-src1/renamed-src1": {}, + "/usr/include/src2": {}, + "/usr/include/header-subpath-src5/renamed-src5": { + IsDir: true, + }, + "/usr/include/src6": { + IsDir: true, + }, }, }, }, diff --git a/website/docs/artifacts.md b/website/docs/artifacts.md index f20b5b1fc..a7869a1b7 100644 --- a/website/docs/artifacts.md +++ b/website/docs/artifacts.md @@ -232,3 +232,32 @@ artifacts: - source: /usr/lib/golang/go dest: /usr/bin/go ``` + +### Headers + +Headers are header to be included with the package. On Linux these typically go +under `/usr/include/`. + +Headers are a mapping of file path to [artifact configuration](#artifact-configuration). +The file path is the path to a file or directory that must be available after +the build section has finished. This path is relative to the working directory +of the build phase *before* any directory changes are made. + +```yaml +artifacts: + headers: + src/my_header.h: +``` + +or for a directory: + +```yaml +artifacts: + headers: + src/my_headers/: +``` + +Note that headers are not installed within a subdirectory of `/usr/include/` +with the name of the package. They are installed directly into `/usr/include/`. +For instance, for the above examples, the headers would be installed to +`/usr/include/my_header.h` and `/usr/include/my_headers/` respectively.