package rosa import ( "context" "encoding/json" "errors" "net/http" "strconv" "sync" "hakurei.app/internal/pkg" ) // PArtifact is a lazily-initialised [pkg.Artifact] preset. type PArtifact int const ( LLVMCompilerRT PArtifact = iota LLVMRuntimes LLVMClang // ImageInitramfs is the Rosa OS initramfs archive. ImageInitramfs // Kernel is the generic Rosa OS Linux kernel. Kernel // KernelHeaders is an installation of kernel headers for [Kernel]. KernelHeaders // KernelSource is a writable kernel source tree installed to [AbsUsrSrc]. KernelSource ACL ArgpStandalone Attr Autoconf Automake BC Bash Binutils Bison Bzip2 CMake Coreutils Curl DTC Diffutils Elfutils Fakeroot Findutils Flex Fuse GMP GLib Gawk GenInitCPIO Gettext Git Go Gperf Grep Gzip Hakurei HakureiDist Kmod LibXau Libcap Libexpat Libiconv Libpsl Libffi Libgd Libtool Libseccomp Libucontext Libxml2 Libxslt M4 MPC MPFR Make Meson Mksh MuslFts MuslObstack NSS NSSCACert Ncurses Ninja OpenSSL PCRE2 Patch Perl PerlLocaleGettext PerlMIMECharset PerlModuleBuild PerlPodParser PerlSGMLS PerlTermReadKey PerlTextCharWidth PerlTextWrapI18N PerlUnicodeGCString PerlYAMLTiny PkgConfig Procps Python PythonIniConfig PythonPackaging PythonPluggy PythonPyTest PythonPygments QEMU Rsync Sed Setuptools SquashfsTools TamaGo Tar Texinfo Toybox toyboxEarly Unzip UtilLinux Wayland WaylandProtocols XCB XCBProto Xproto XZ Zlib Zstd // PresetUnexportedStart is the first unexported preset. PresetUnexportedStart buildcatrust = iota - 1 utilMacros // Musl is a standalone libc that does not depend on the toolchain. Musl // gcc is a hacked-to-pieces GCC toolchain meant for use in intermediate // stages only. This preset and its direct output must never be exposed. gcc // Stage0 is a tarball containing all compile-time dependencies of artifacts // part of the [Std] toolchain. Stage0 // PresetEnd is the total number of presets and does not denote a preset. PresetEnd ) // Metadata is stage-agnostic information of a [PArtifact] not directly // representable in the resulting [pkg.Artifact]. type Metadata struct { f func(t Toolchain) (a pkg.Artifact, version string) // Unique package name. Name string `json:"name"` // Short user-facing description. Description string `json:"description"` // Project home page. Website string `json:"website,omitempty"` // Project identifier on [Anitya]. // // [Anitya]: https://release-monitoring.org/ ID int `json:"-"` // Optional custom version checking behaviour. latest func(v *Versions, cur string) bool } // IsLatest returns whether cur is the latest version described by v. func (meta *Metadata) IsLatest(v *Versions, cur string) bool { if meta.latest != nil { return meta.latest(v, cur) } return v.Latest == cur } // Unversioned denotes an unversioned [PArtifact]. const Unversioned = "\x00" // UnpopulatedIDError is returned by [Metadata.GetLatest] for an instance of // [Metadata] where ID is not populated. type UnpopulatedIDError struct{} func (UnpopulatedIDError) Unwrap() error { return errors.ErrUnsupported } func (UnpopulatedIDError) Error() string { return "Anitya ID is not populated" } // Versions are package versions returned by Anitya. type Versions struct { // The latest version for the project, as determined by the version sorting algorithm. Latest string `json:"latest_version"` // List of all versions that aren’t flagged as pre-release. Stable []string `json:"stable_versions"` // List of all versions stored, sorted from newest to oldest. All []string `json:"versions"` } // checkStable returns whether cur is the latest version described by Stable. func (v *Versions) checkStable(cur string) bool { if len(v.Stable) == 0 { return v.Latest == cur } return v.Stable[0] == cur } // GetVersions returns versions fetched from Anitya. func (meta *Metadata) GetVersions(ctx context.Context) (*Versions, error) { if meta.ID == 0 { return nil, UnpopulatedIDError{} } var resp *http.Response if req, err := http.NewRequestWithContext( ctx, http.MethodGet, "https://release-monitoring.org/api/v2/versions/?project_id="+ strconv.Itoa(meta.ID), nil, ); err != nil { return nil, err } else if resp, err = http.DefaultClient.Do(req); err != nil { return nil, err } var v Versions err := json.NewDecoder(resp.Body).Decode(&v) return &v, errors.Join(err, resp.Body.Close()) } var ( // artifactsM is an array of [PArtifact] metadata. artifactsM [PresetEnd]Metadata // artifacts stores the result of Metadata.f. artifacts [_toolchainEnd][len(artifactsM)]pkg.Artifact // versions stores the version of [PArtifact]. versions [_toolchainEnd][len(artifactsM)]string // artifactsOnce is for lazy initialisation of artifacts. artifactsOnce [_toolchainEnd][len(artifactsM)]sync.Once ) // GetMetadata returns [Metadata] of a [PArtifact]. func GetMetadata(p PArtifact) *Metadata { return &artifactsM[p] } // Load returns the resulting [pkg.Artifact] of [PArtifact]. func (t Toolchain) Load(p PArtifact) pkg.Artifact { artifactsOnce[t][p].Do(func() { artifacts[t][p], versions[t][p] = artifactsM[p].f(t) }) return artifacts[t][p] } // Version returns the version string of [PArtifact]. func (t Toolchain) Version(p PArtifact) string { artifactsOnce[t][p].Do(func() { artifacts[t][p], versions[t][p] = artifactsM[p].f(t) }) return versions[t][p] } // ResolveName returns a [PArtifact] by name. func ResolveName(name string) (p PArtifact, ok bool) { for i := range PresetUnexportedStart { if name == artifactsM[i].Name { return i, true } } return 0, false }