mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-11-04 00:11:04 +00:00 
			
		
		
		
	- While doing cross-compiling of Forgejo (with CGO enabled) it was noticed that besides compiling sqlite3, github.com/DataDog/zstd (a CGO wrapper around the reference zstd library) took a long time to compile. Upon investigating why this library was included in the first place I concluded that this library is not even used and compiling this package is a waste of time and CPU cycles. - https://github.com/sassoftware/go-rpmutils is the library that uses the CGO zstd library, and would use it the pure Go variant (https://github.com/klauspost/compress/zstd) if CGO is enabled (which is the default). It uses zstd to uncompress the payload of a RPM packages. This is a operation that Forgejo does not use in the slightest, hence being unused code. - It is not possible to force compiling the pure Go variant if CGO is enabled. Therefore forking and removing this code is the only option to avoid compiling the zstd C library. The changes made to the fork can be seen here: https://code.forgejo.org/forgejo/go-rpmutils/compare/2660c86d578cd2a5366fe5bda488e37f9ab1b11a...v1.0.0 - Via [actiongraph](https://github.com/icio/actiongraph) you can precisely see where the Go compiler is spending time, on a beefy machine the compilation takes of the zstd C library takes ~40s. For reference compiling the sqlite3 C library takes ~50s. - The forgejo binary (build via `make backend`) reduced its size by 835776 bytes (0.8 MiB). TL;DR forked library to remove unused code to avoid Go spending a considerable amount of time compiling the reference zstd library that in the end is never used. <!--start release-notes-assistant--> ## Release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Features - [PR](https://codeberg.org/forgejo/forgejo/pulls/7953): <!--number 7953 --><!--line 0 --><!--description cmVwbGFjZSBnby1ycG11dGlscyBsaWJyYXJ5IHdpdGggb3VyIG93bg==-->replace go-rpmutils library with our own<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7953 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Gusted <postmaster@gusted.xyz> Co-committed-by: Gusted <postmaster@gusted.xyz>
		
			
				
	
	
		
			310 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			310 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package rpm
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"forgejo.org/modules/timeutil"
 | 
						|
	"forgejo.org/modules/validation"
 | 
						|
 | 
						|
	"code.forgejo.org/forgejo/go-rpmutils"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	PropertyMetadata     = "rpm.metadata"
 | 
						|
	PropertyGroup        = "rpm.group"
 | 
						|
	PropertyArchitecture = "rpm.architecture"
 | 
						|
 | 
						|
	SettingKeyPrivate = "rpm.key.private"
 | 
						|
	SettingKeyPublic  = "rpm.key.public"
 | 
						|
 | 
						|
	RepositoryPackage = "_rpm"
 | 
						|
	RepositoryVersion = "_repository"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// Can't use the syscall constants because they are not available for windows build.
 | 
						|
	sIFMT  = 0xf000
 | 
						|
	sIFDIR = 0x4000
 | 
						|
	sIXUSR = 0x40
 | 
						|
	sIXGRP = 0x8
 | 
						|
	sIXOTH = 0x1
 | 
						|
)
 | 
						|
 | 
						|
// https://rpm-software-management.github.io/rpm/manual/spec.html
 | 
						|
// https://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/pkgformat.html
 | 
						|
 | 
						|
type Package struct {
 | 
						|
	Name            string
 | 
						|
	Version         string
 | 
						|
	VersionMetadata *VersionMetadata
 | 
						|
	FileMetadata    *FileMetadata
 | 
						|
}
 | 
						|
 | 
						|
type VersionMetadata struct {
 | 
						|
	License     string `json:"license,omitempty"`
 | 
						|
	ProjectURL  string `json:"project_url,omitempty"`
 | 
						|
	Summary     string `json:"summary,omitempty"`
 | 
						|
	Description string `json:"description,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
type FileMetadata struct {
 | 
						|
	Architecture  string `json:"architecture,omitempty"`
 | 
						|
	Epoch         string `json:"epoch,omitempty"`
 | 
						|
	Version       string `json:"version,omitempty"`
 | 
						|
	Release       string `json:"release,omitempty"`
 | 
						|
	Vendor        string `json:"vendor,omitempty"`
 | 
						|
	Group         string `json:"group,omitempty"`
 | 
						|
	Packager      string `json:"packager,omitempty"`
 | 
						|
	SourceRpm     string `json:"source_rpm,omitempty"`
 | 
						|
	BuildHost     string `json:"build_host,omitempty"`
 | 
						|
	BuildTime     uint64 `json:"build_time,omitempty"`
 | 
						|
	FileTime      uint64 `json:"file_time,omitempty"`
 | 
						|
	InstalledSize uint64 `json:"installed_size,omitempty"`
 | 
						|
	ArchiveSize   uint64 `json:"archive_size,omitempty"`
 | 
						|
 | 
						|
	Provides  []*Entry `json:"provide,omitempty"`
 | 
						|
	Requires  []*Entry `json:"require,omitempty"`
 | 
						|
	Conflicts []*Entry `json:"conflict,omitempty"`
 | 
						|
	Obsoletes []*Entry `json:"obsolete,omitempty"`
 | 
						|
 | 
						|
	Files []*File `json:"files,omitempty"`
 | 
						|
 | 
						|
	Changelogs []*Changelog `json:"changelogs,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
type Entry struct {
 | 
						|
	Name     string `json:"name" xml:"name,attr"`
 | 
						|
	Flags    string `json:"flags,omitempty" xml:"flags,attr,omitempty"`
 | 
						|
	AltFlags uint32 `json:"alt_flags,omitempty" xml:"alt_flags,attr,omitempty"`
 | 
						|
	Version  string `json:"version,omitempty" xml:"ver,attr,omitempty"`
 | 
						|
	Epoch    string `json:"epoch,omitempty" xml:"epoch,attr,omitempty"`
 | 
						|
	Release  string `json:"release,omitempty" xml:"rel,attr,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
type File struct {
 | 
						|
	Path         string `json:"path" xml:",chardata"`
 | 
						|
	Type         string `json:"type,omitempty" xml:"type,attr,omitempty"`
 | 
						|
	IsExecutable bool   `json:"is_executable" xml:"-"`
 | 
						|
}
 | 
						|
 | 
						|
type Changelog struct {
 | 
						|
	Author string             `json:"author,omitempty" xml:"author,attr"`
 | 
						|
	Date   timeutil.TimeStamp `json:"date,omitempty" xml:"date,attr"`
 | 
						|
	Text   string             `json:"text,omitempty" xml:",chardata"`
 | 
						|
}
 | 
						|
 | 
						|
// ParsePackage parses the RPM package file
 | 
						|
func ParsePackage(r io.Reader, repoType string) (*Package, error) {
 | 
						|
	rpm, err := rpmutils.ReadRpm(r)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	nevra, err := rpm.Header.GetNEVRA()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	version := fmt.Sprintf("%s-%s", nevra.Version, nevra.Release)
 | 
						|
	if nevra.Epoch != "" && nevra.Epoch != "0" {
 | 
						|
		version = fmt.Sprintf("%s-%s", nevra.Epoch, version)
 | 
						|
	}
 | 
						|
 | 
						|
	p := &Package{
 | 
						|
		Name:    nevra.Name,
 | 
						|
		Version: version,
 | 
						|
		VersionMetadata: &VersionMetadata{
 | 
						|
			Summary:     getString(rpm.Header, rpmutils.SUMMARY),
 | 
						|
			Description: getString(rpm.Header, rpmutils.DESCRIPTION),
 | 
						|
			License:     getString(rpm.Header, rpmutils.LICENSE),
 | 
						|
			ProjectURL:  getString(rpm.Header, rpmutils.URL),
 | 
						|
		},
 | 
						|
		FileMetadata: &FileMetadata{
 | 
						|
			Architecture:  nevra.Arch,
 | 
						|
			Epoch:         nevra.Epoch,
 | 
						|
			Version:       nevra.Version,
 | 
						|
			Release:       nevra.Release,
 | 
						|
			Vendor:        getString(rpm.Header, rpmutils.VENDOR),
 | 
						|
			Group:         getString(rpm.Header, rpmutils.GROUP),
 | 
						|
			Packager:      getString(rpm.Header, rpmutils.PACKAGER),
 | 
						|
			SourceRpm:     getString(rpm.Header, rpmutils.SOURCERPM),
 | 
						|
			BuildHost:     getString(rpm.Header, rpmutils.BUILDHOST),
 | 
						|
			BuildTime:     getUInt64(rpm.Header, rpmutils.BUILDTIME),
 | 
						|
			FileTime:      getUInt64(rpm.Header, rpmutils.FILEMTIMES),
 | 
						|
			InstalledSize: getUInt64(rpm.Header, rpmutils.SIZE),
 | 
						|
			ArchiveSize:   getUInt64(rpm.Header, rpmutils.SIG_PAYLOADSIZE),
 | 
						|
 | 
						|
			Provides:   getEntries(rpm.Header, rpmutils.PROVIDENAME, rpmutils.PROVIDEVERSION, rpmutils.PROVIDEFLAGS, repoType),
 | 
						|
			Requires:   getEntries(rpm.Header, rpmutils.REQUIRENAME, rpmutils.REQUIREVERSION, rpmutils.REQUIREFLAGS, repoType),
 | 
						|
			Conflicts:  getEntries(rpm.Header, rpmutils.CONFLICTNAME, rpmutils.CONFLICTVERSION, rpmutils.CONFLICTFLAGS, repoType),
 | 
						|
			Obsoletes:  getEntries(rpm.Header, rpmutils.OBSOLETENAME, rpmutils.OBSOLETEVERSION, rpmutils.OBSOLETEFLAGS, repoType),
 | 
						|
			Files:      getFiles(rpm.Header),
 | 
						|
			Changelogs: getChangelogs(rpm.Header),
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	if !validation.IsValidURL(p.VersionMetadata.ProjectURL) {
 | 
						|
		p.VersionMetadata.ProjectURL = ""
 | 
						|
	}
 | 
						|
 | 
						|
	return p, nil
 | 
						|
}
 | 
						|
 | 
						|
func getString(h *rpmutils.RpmHeader, tag int) string {
 | 
						|
	values, err := h.GetStrings(tag)
 | 
						|
	if err != nil || len(values) < 1 {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	return values[0]
 | 
						|
}
 | 
						|
 | 
						|
func getUInt64(h *rpmutils.RpmHeader, tag int) uint64 {
 | 
						|
	values, err := h.GetUint64s(tag)
 | 
						|
	if err != nil || len(values) < 1 {
 | 
						|
		return 0
 | 
						|
	}
 | 
						|
	return values[0]
 | 
						|
}
 | 
						|
 | 
						|
func getEntries(h *rpmutils.RpmHeader, namesTag, versionsTag, flagsTag int, repoType string) []*Entry {
 | 
						|
	names, err := h.GetStrings(namesTag)
 | 
						|
	if err != nil || len(names) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	flags, err := h.GetUint64s(flagsTag)
 | 
						|
	if err != nil || len(flags) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	versions, err := h.GetStrings(versionsTag)
 | 
						|
	if err != nil || len(versions) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	if len(names) != len(flags) || len(names) != len(versions) {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	entries := make([]*Entry, 0, len(names))
 | 
						|
 | 
						|
	switch repoType {
 | 
						|
	case "rpm":
 | 
						|
		for i := range names {
 | 
						|
			e := &Entry{
 | 
						|
				Name: names[i],
 | 
						|
			}
 | 
						|
 | 
						|
			flags := flags[i]
 | 
						|
			if (flags&rpmutils.RPMSENSE_GREATER) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 {
 | 
						|
				e.Flags = "GE"
 | 
						|
			} else if (flags&rpmutils.RPMSENSE_LESS) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 {
 | 
						|
				e.Flags = "LE"
 | 
						|
			} else if (flags & rpmutils.RPMSENSE_GREATER) != 0 {
 | 
						|
				e.Flags = "GT"
 | 
						|
			} else if (flags & rpmutils.RPMSENSE_LESS) != 0 {
 | 
						|
				e.Flags = "LT"
 | 
						|
			} else if (flags & rpmutils.RPMSENSE_EQUAL) != 0 {
 | 
						|
				e.Flags = "EQ"
 | 
						|
			}
 | 
						|
 | 
						|
			version := versions[i]
 | 
						|
			if version != "" {
 | 
						|
				parts := strings.Split(version, "-")
 | 
						|
 | 
						|
				versionParts := strings.Split(parts[0], ":")
 | 
						|
				if len(versionParts) == 2 {
 | 
						|
					e.Version = versionParts[1]
 | 
						|
					e.Epoch = versionParts[0]
 | 
						|
				} else {
 | 
						|
					e.Version = versionParts[0]
 | 
						|
					e.Epoch = "0"
 | 
						|
				}
 | 
						|
 | 
						|
				if len(parts) > 1 {
 | 
						|
					e.Release = parts[1]
 | 
						|
				}
 | 
						|
			}
 | 
						|
			entries = append(entries, e)
 | 
						|
		}
 | 
						|
	case "alt":
 | 
						|
		for i := range names {
 | 
						|
			e := &Entry{
 | 
						|
				AltFlags: uint32(flags[i]),
 | 
						|
			}
 | 
						|
			e.Version = versions[i]
 | 
						|
			entries = append(entries, e)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return entries
 | 
						|
}
 | 
						|
 | 
						|
func getFiles(h *rpmutils.RpmHeader) []*File {
 | 
						|
	baseNames, _ := h.GetStrings(rpmutils.BASENAMES)
 | 
						|
	dirNames, _ := h.GetStrings(rpmutils.DIRNAMES)
 | 
						|
	dirIndexes, _ := h.GetUint32s(rpmutils.DIRINDEXES)
 | 
						|
	fileFlags, _ := h.GetUint32s(rpmutils.FILEFLAGS)
 | 
						|
	fileModes, _ := h.GetUint32s(rpmutils.FILEMODES)
 | 
						|
 | 
						|
	files := make([]*File, 0, len(baseNames))
 | 
						|
	for i := range baseNames {
 | 
						|
		if len(dirIndexes) <= i {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		dirIndex := dirIndexes[i]
 | 
						|
		if len(dirNames) <= int(dirIndex) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		var fileType string
 | 
						|
		var isExecutable bool
 | 
						|
		if i < len(fileFlags) && (fileFlags[i]&rpmutils.RPMFILE_GHOST) != 0 {
 | 
						|
			fileType = "ghost"
 | 
						|
		} else if i < len(fileModes) {
 | 
						|
			if (fileModes[i] & sIFMT) == sIFDIR {
 | 
						|
				fileType = "dir"
 | 
						|
			} else {
 | 
						|
				mode := fileModes[i] & ^uint32(sIFMT)
 | 
						|
				isExecutable = (mode&sIXUSR) != 0 || (mode&sIXGRP) != 0 || (mode&sIXOTH) != 0
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		files = append(files, &File{
 | 
						|
			Path:         dirNames[dirIndex] + baseNames[i],
 | 
						|
			Type:         fileType,
 | 
						|
			IsExecutable: isExecutable,
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	return files
 | 
						|
}
 | 
						|
 | 
						|
func getChangelogs(h *rpmutils.RpmHeader) []*Changelog {
 | 
						|
	texts, err := h.GetStrings(rpmutils.CHANGELOGTEXT)
 | 
						|
	if err != nil || len(texts) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	authors, err := h.GetStrings(rpmutils.CHANGELOGNAME)
 | 
						|
	if err != nil || len(authors) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	times, err := h.GetUint32s(rpmutils.CHANGELOGTIME)
 | 
						|
	if err != nil || len(times) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	if len(texts) != len(authors) || len(texts) != len(times) {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	changelogs := make([]*Changelog, 0, len(texts))
 | 
						|
	for i := range texts {
 | 
						|
		changelogs = append(changelogs, &Changelog{
 | 
						|
			Author: authors[i],
 | 
						|
			Date:   timeutil.TimeStamp(times[i]),
 | 
						|
			Text:   texts[i],
 | 
						|
		})
 | 
						|
	}
 | 
						|
	return changelogs
 | 
						|
}
 |