mirror of
https://github.com/goreleaser/nfpm
synced 2026-10-31 10:41:33 +01:00
rpmbuild runs script.req on the executable files of a package, so a package that ships a "#!/bin/bash" script requires /bin/bash without any work from the packager. nfpm has no such generator, so the interpreter of every script must be listed by hand, which is error-prone. Add the opt-in rpm.script_requires option. Like script.req with the exeonly flag of script.attr, it reads the "#!" line of each executable file and requires its interpreter, and the absolute program run by env, flagged RPMSENSE_FIND_REQUIRES like the dependencies rpmbuild generates. Documentation files, symlinks, directories and ghosts are skipped. The mode checked is the one in the package, so file_info.mode is honored and the option also works when building on Windows. The generated spec sets AutoReqProv: no, so the requires are written to it explicitly and rpmbuild --rebuild keeps them. Add unit tests, a scriptrequires acceptance stage that reads the requires back with rpm, and an executable script to the srpm rebuild acceptance test. Co-authored-by: Laurent Coustet <laurent.coustet@bluemind.net>
89 lines
2.4 KiB
Go
89 lines
2.4 KiB
Go
package rpm
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"regexp"
|
|
"slices"
|
|
|
|
"github.com/goreleaser/nfpm/v2"
|
|
"github.com/goreleaser/nfpm/v2/files"
|
|
)
|
|
|
|
// rpmSenseFindRequires marks a dependency as generated, like the ones added by
|
|
// the rpmbuild dependency generators.
|
|
// https://github.com/rpm-software-management/rpm/blob/master/include/rpm/rpmds.h
|
|
const rpmSenseFindRequires = 1 << 14
|
|
|
|
// The two expressions of rpm's script.req: the interpreter of the #! line, and
|
|
// the absolute path of the program run by env.
|
|
var (
|
|
shebangInterpreter = regexp.MustCompile(`^#![[:space:]]*(/[^[:space:]]+)`)
|
|
shebangEnvProgram = regexp.MustCompile(`^#![[:space:]]*[^[:space:]]*/bin/env[[:space:]]+(/[^[:space:]]+)`)
|
|
)
|
|
|
|
// scriptRequires returns the interpreters of the executable scripts of the
|
|
// package, read from their #! line.
|
|
func scriptRequires(info *nfpm.Info) ([]string, error) {
|
|
if !info.RPM.ScriptRequires {
|
|
return nil, nil
|
|
}
|
|
|
|
var interpreters []string
|
|
for _, content := range info.Contents {
|
|
if content.Packager != "" && content.Packager != contentPackager {
|
|
continue
|
|
}
|
|
switch content.Type {
|
|
case files.TypeSymlink, files.TypeDir, files.TypeImplicitDir, files.TypeRPMGhost:
|
|
continue
|
|
case files.TypeRPMDoc, files.TypeRPMReadme:
|
|
// rpmbuild generates no dependencies for documentation.
|
|
continue
|
|
}
|
|
// rpmbuild checks the mode of the file in the build root; the mode
|
|
// in the package is the nfpm equivalent, and the only one that can
|
|
// be executable when building on Windows.
|
|
if content.FileInfo.Mode.Perm()&0o111 == 0 {
|
|
continue
|
|
}
|
|
|
|
found, err := shebangRequires(content.Source)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not read the interpreter of %s: %w", content.Source, err)
|
|
}
|
|
interpreters = append(interpreters, found...)
|
|
}
|
|
|
|
slices.Sort(interpreters)
|
|
return slices.Compact(interpreters), nil
|
|
}
|
|
|
|
func shebangRequires(path string) ([]string, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
r := bufio.NewReader(f)
|
|
if magic, err := r.Peek(2); err != nil || string(magic) != "#!" {
|
|
return nil, nil
|
|
}
|
|
line, err := r.ReadString('\n')
|
|
if err != nil && !errors.Is(err, io.EOF) {
|
|
return nil, err
|
|
}
|
|
|
|
var found []string
|
|
for _, re := range []*regexp.Regexp{shebangInterpreter, shebangEnvProgram} {
|
|
if m := re.FindStringSubmatch(line); m != nil {
|
|
found = append(found, m[1])
|
|
}
|
|
}
|
|
return found, nil
|
|
}
|