packaging/linux/deb/pkg.go (144 lines of code) (raw):

package deb import ( "context" "fmt" "path/filepath" "github.com/Azure/dalec" "github.com/moby/buildkit/client/llb" "github.com/pkg/errors" ) const ( // Unique name that would not normally be in the spec // This will get used to create the source tar for go module deps gomodsName = "xxxdalecGomodsInternal" // Unique name that would not normally be in the spec // This will get used to create the source tar for cargo deps cargohomeName = "xxxdalecCargoHomeInternal" ) func mountSources(sources map[string]llb.State, dir string, mod func(string) string) llb.RunOption { return dalec.RunOptFunc(func(ei *llb.ExecInfo) { for key, src := range sources { if mod != nil { key = mod(key) } llb.AddMount(filepath.Join(dir, key), src).SetRunOption(ei) } }) } var errMissingRequiredField = fmt.Errorf("missing required field") func validateSpec(spec *dalec.Spec) error { if spec.Packager == "" { return errors.Wrap(errMissingRequiredField, "packager") } return nil } // Dalec patches apply directly to each individual source tree, e.g. `cd <src>; patch ...` // Debian applies patches from 1 directory up from the source tree (e.g. no `cd` as above). // As such the patch files are not formatted correctly for Debian's build tooling. // Here we generate a single patch file that generates the correct format. // // This way dpkg-source can automatically apply patches for us, and informs // the caller of the patches applied and is generally just more inline with // a typical deb build. // // This is using git instead of raw `diff` or other standalone tooling because only git appears to preserve permissions for new files. // As an example, if patch adds a new file with mode +x, `diff` will not see the permissions for that new file. func createPatches(spec *dalec.Spec, sources map[string]llb.State, worker llb.State, dr llb.State, opts ...llb.ConstraintsOpt) llb.State { patches := llb.Scratch() if len(spec.Patches) > 0 { patchesMountInput := llb.Scratch(). File(llb.Mkfile("dalec-changes.patch", 0o600, patchHeader)) patches = worker. Run(dalec.ShArgs("set -e; git config --global user.email phony; git config --global user.name Dalec")). Run( dalec.ShArgs("set -e; git init .; git add .; git commit -m 'Initial commit'; \"${DEBIAN_DIR}/dalec/patch.sh\"; git add .; git commit -m 'With patch'; git diff HEAD~1 >> /work/out/dalec-changes.patch; echo 'dalec-changes.patch' > /work/out/series"), llb.Dir("/work/sources"), mountSources(sources, "/work/sources", nil), // DEBIAN_DIR is used by the patch script to find the debian directory where we actually have the patches llb.AddEnv("DEBIAN_DIR", "/work/debian"), llb.AddMount("/work/debian", dr, llb.SourcePath("debian"), llb.Readonly), dalec.WithConstraints(opts...), ).AddMount("/work/out", patchesMountInput) } return patches } func SourcePackage(ctx context.Context, sOpt dalec.SourceOpts, worker llb.State, spec *dalec.Spec, targetKey, distroVersionID string, cfg SourcePkgConfig, opts ...llb.ConstraintsOpt) (llb.State, error) { if err := validateSpec(spec); err != nil { return llb.Scratch(), err } dr, err := Debroot(ctx, sOpt, spec, worker, llb.Scratch(), targetKey, "", distroVersionID, cfg, opts...) if err != nil { return llb.Scratch(), err } sources, err := dalec.Sources(spec, sOpt) if err != nil { return llb.Scratch(), err } gomodSt, err := spec.GomodDeps(sOpt, worker, opts...) if err != nil { return llb.Scratch(), errors.Wrap(err, "error preparing gomod deps") } cargohomeSt, err := spec.CargohomeDeps(sOpt, worker, opts...) if err != nil { return llb.Scratch(), errors.Wrap(err, "error preparing cargohome deps") } if gomodSt != nil { sources[gomodsName] = *gomodSt } if cargohomeSt != nil { sources[cargohomeName] = *cargohomeSt } patches := createPatches(spec, sources, worker, dr, opts...) work := worker.Run( dalec.ShArgs("set -e; ls -lh; dpkg-buildpackage -S -us -uc; mkdir -p /tmp/out; ls -lh; cp -r /work/"+spec.Name+"_"+spec.Version+"* /tmp/out; ls -lh /tmp/out"), llb.Dir("/work/pkg"), llb.AddMount("/work/pkg/debian", dr, llb.SourcePath("debian")), // This cannot be readonly because the debian directory gets modified by dpkg-buildpackage llb.AddMount("/work/pkg/debian/patches", patches, llb.Readonly), llb.AddEnv("DH_VERBOSE", "1"), dalec.RunOptFunc(func(ei *llb.ExecInfo) { debSources := TarDebSources(worker, spec, sources, "src.tar.gz", sOpt, opts...) llb.AddMount("/work/"+spec.Name+"_"+spec.Version+".orig.tar.gz", debSources, llb.SourcePath("src.tar.gz")).SetRunOption(ei) }), dalec.WithConstraints(opts...), ) return work.AddMount("/tmp/out", llb.Scratch()), nil } func BuildDebBinaryOnly(worker llb.State, spec *dalec.Spec, debroot llb.State, distroVersionID string, opts ...llb.ConstraintsOpt) (llb.State, error) { dirName := filepath.Join("/work", spec.Name+"_"+spec.Version+"-"+spec.Revision) st := worker. Run( dalec.ShArgs("set -e; ls -lh; dpkg-buildpackage -b -uc -us; mkdir -p /tmp/out; cp ../*.deb /tmp/out; ls -lh /tmp/out"), llb.Dir(dirName), llb.AddEnv("DH_VERBOSE", "1"), llb.AddMount(dirName, debroot), dalec.WithConstraints(opts...), ).AddMount("/tmp/out", llb.Scratch()) return dalec.MergeAtPath(llb.Scratch(), []llb.State{st}, "/"), nil } func BuildDeb(worker llb.State, spec *dalec.Spec, srcPkg llb.State, distroVersionID string, opts ...llb.ConstraintsOpt) (llb.State, error) { dirName := filepath.Join("/work", spec.Name+"_"+spec.Version+"-"+spec.Revision) st := worker. Run( dalec.ShArgs("set -e; ls -lh; dpkg-source -x ./*.dsc; ls -lh; cd "+spec.Name+"-"+spec.Version+"; ls -lh *; dpkg-buildpackage -b -uc -us; mkdir -p /tmp/out; cp ../*.deb /tmp/out; ls -lh /tmp/out"), llb.Dir(dirName), llb.AddEnv("DH_VERBOSE", "1"), llb.AddMount(dirName, srcPkg), dalec.WithConstraints(opts...), ).AddMount("/tmp/out", llb.Scratch()) return dalec.MergeAtPath(llb.Scratch(), []llb.State{st, srcPkg}, "/"), nil } func TarDebSources(work llb.State, spec *dalec.Spec, srcStates map[string]llb.State, dest string, sOpts dalec.SourceOpts, opts ...llb.ConstraintsOpt) llb.State { outBase := "/tmp/out" out := filepath.Join(outBase, filepath.Dir(dest)) worker := work.Run( llb.AddMount("/src", llb.Scratch()), dalec.RunOptFunc(func(ei *llb.ExecInfo) { for key, state := range srcStates { mountOpts := []llb.MountOption{} src, ok := spec.Sources[key] // If the source is not explicitly listed in the spec sources, assume it is a directory (e.g., for gomod dependencies) isDir := true if ok { isDir = dalec.SourceIsDir(src) } if !isDir { mountOpts = append(mountOpts, llb.SourcePath(filepath.Join("/", key))) } // If the tar contains only a single directory, dpkg will extract its contents directly into the root directory. mounthPath := filepath.Join("/src", key) if len(srcStates) == 1 && isDir { mounthPath = filepath.Join("/src", key, key) } llb.AddMount(mounthPath, state, mountOpts...).SetRunOption(ei) } }), dalec.ShArgs("tar -C /src -cvzf /tmp/st ."), dalec.WithConstraints(opts...), ).Run( llb.Args([]string{"/bin/sh", "-c", "mkdir -p " + out + " && mv /tmp/st " + filepath.Join(out, filepath.Base(dest))}), dalec.WithConstraints(opts...), ) return worker.AddMount(outBase, llb.Scratch()) }