forked from security/hakurei
Compare commits
17 Commits
pkgserver
...
pkgserver-
| Author | SHA1 | Date | |
|---|---|---|---|
| 33e11856c6 | |||
| 0f944f7a0e | |||
| 223037e7c2 | |||
| acecad7f75 | |||
| 4f82f28c73 | |||
| ae07e0127b | |||
| d2696a6f30 | |||
| 17ba70771c | |||
| 93984f29da | |||
| d7cd746b43 | |||
| b255f07b0f | |||
| dec4cdd068 | |||
| 73c620ecd5 | |||
| 69467a1542 | |||
| 1ae6a35bc8 | |||
| 9ef5b52b85 | |||
| f93158cb3c |
@@ -1,112 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"hakurei.app/internal/rosa"
|
|
||||||
)
|
|
||||||
|
|
||||||
func serveCount(index *PackageIndex) func(http.ResponseWriter, *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
count := len(index.names)
|
|
||||||
w.Write([]byte(strconv.Itoa(count)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveStatus(index *PackageIndex) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if index == nil {
|
|
||||||
http.Error(w, "index is nil", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
base := path.Base(r.URL.Path)
|
|
||||||
name := strings.TrimSuffix(base, ".log")
|
|
||||||
p, ok := rosa.ResolveName(name)
|
|
||||||
if !ok {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m := rosa.GetMetadata(p)
|
|
||||||
pk, ok := index.names[m.Name]
|
|
||||||
if !ok {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(pk.status) > 0 {
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
_, err := io.Copy(w, bytes.NewReader(pk.status))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetPayload struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
Values []PackageIndexEntry `json:"values"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGetPayload(values []*PackageIndexEntry) GetPayload {
|
|
||||||
count := len(values)
|
|
||||||
v := make([]PackageIndexEntry, count)
|
|
||||||
for i, _ := range values {
|
|
||||||
v[i] = *values[i]
|
|
||||||
}
|
|
||||||
return GetPayload{
|
|
||||||
Count: count,
|
|
||||||
Values: v,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveGet(index *PackageIndex) func(http.ResponseWriter, *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
q := r.URL.Query()
|
|
||||||
limit, err := strconv.Atoi(q.Get("limit"))
|
|
||||||
if err != nil || limit > 100 || limit < 1 {
|
|
||||||
http.Error(w, fmt.Sprintf("limit must be an integer between 1 and 100"), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
i, err := strconv.Atoi(q.Get("index"))
|
|
||||||
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
|
||||||
http.Error(w, fmt.Sprintf("index must be an integer between 0 and %d", len(index.sorts[0])-1), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sort, err := strconv.Atoi(q.Get("sort"))
|
|
||||||
if err != nil || sort >= len(index.sorts) || sort < 0 {
|
|
||||||
http.Error(w, fmt.Sprintf("sort must be an integer between 0 and %d", len(index.sorts)-1), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
values := index.sorts[sort][i:min(i+limit, len(index.sorts[sort]))]
|
|
||||||
payload := NewGetPayload(values)
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
b, err := json.Marshal(payload)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
_, err = bytes.NewBuffer(b).WriteTo(w)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func apiRoutes(index *PackageIndex) {
|
|
||||||
http.HandleFunc("GET /api/count", serveCount(index))
|
|
||||||
http.HandleFunc("GET /api/get", serveGet(index))
|
|
||||||
http.HandleFunc("GET /api/status/", serveStatus(index))
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cmp"
|
|
||||||
"fmt"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"hakurei.app/internal/pkg"
|
|
||||||
"hakurei.app/internal/rosa"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SortOrders int
|
|
||||||
|
|
||||||
const (
|
|
||||||
DeclarationAscending SortOrders = iota
|
|
||||||
DeclarationDescending
|
|
||||||
NameAscending
|
|
||||||
NameDescending
|
|
||||||
limitSortOrders
|
|
||||||
)
|
|
||||||
|
|
||||||
type PackageIndex struct {
|
|
||||||
sorts [limitSortOrders][rosa.PresetUnexportedStart]*PackageIndexEntry
|
|
||||||
names map[string]*PackageIndexEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
type PackageIndexEntry struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Website string `json:"website,omitempty"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
Status string `json:"report,omitempty"`
|
|
||||||
status []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPackageIndex(cache *pkg.Cache, report *rosa.Report) (_ *PackageIndex, err error) {
|
|
||||||
index := new(PackageIndex)
|
|
||||||
index.names = make(map[string]*PackageIndexEntry, rosa.PresetUnexportedStart)
|
|
||||||
work := make([]PackageIndexEntry, rosa.PresetUnexportedStart)
|
|
||||||
defer report.HandleAccess(&err)()
|
|
||||||
for p := range rosa.PresetUnexportedStart {
|
|
||||||
m := rosa.GetMetadata(p)
|
|
||||||
v := rosa.Std.Version(p)
|
|
||||||
a := rosa.Std.Load(p)
|
|
||||||
id := cache.Ident(a)
|
|
||||||
st, n := report.ArtifactOf(id)
|
|
||||||
var status []byte
|
|
||||||
var statusUrl string
|
|
||||||
if n < 1 {
|
|
||||||
status = nil
|
|
||||||
statusUrl = ""
|
|
||||||
} else {
|
|
||||||
status = st
|
|
||||||
statusUrl = fmt.Sprintf("/api/status/%s.log", m.Name)
|
|
||||||
}
|
|
||||||
entry := PackageIndexEntry{
|
|
||||||
Name: m.Name,
|
|
||||||
Description: m.Description,
|
|
||||||
Website: m.Website,
|
|
||||||
Version: v,
|
|
||||||
Status: statusUrl,
|
|
||||||
status: status,
|
|
||||||
}
|
|
||||||
work[p] = entry
|
|
||||||
index.names[m.Name] = &entry
|
|
||||||
}
|
|
||||||
for i, p := range work {
|
|
||||||
index.sorts[DeclarationAscending][i] = &p
|
|
||||||
}
|
|
||||||
slices.Reverse(work)
|
|
||||||
for i, p := range work {
|
|
||||||
index.sorts[DeclarationDescending][i] = &p
|
|
||||||
}
|
|
||||||
slices.SortFunc(work, func(a PackageIndexEntry, b PackageIndexEntry) int {
|
|
||||||
return cmp.Compare(a.Name, b.Name)
|
|
||||||
})
|
|
||||||
for i, p := range work {
|
|
||||||
index.sorts[NameAscending][i] = &p
|
|
||||||
}
|
|
||||||
slices.Reverse(work)
|
|
||||||
for i, p := range work {
|
|
||||||
index.sorts[NameDescending][i] = &p
|
|
||||||
}
|
|
||||||
return index, err
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,19 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
@@ -16,6 +23,150 @@ import (
|
|||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:generate sh -c "sass ui/static/dark.scss ui/static/dark.css && sass ui/static/light.scss ui/static/light.css && tsc ui/static/index.ts"
|
||||||
|
//go:embed ui/*
|
||||||
|
var content embed.FS
|
||||||
|
|
||||||
|
func serveWebUI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("serveWebUI: %s\n", r.URL.Path)
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.Header().Set("Pragma", "no-cache")
|
||||||
|
w.Header().Set("Expires", "0")
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
w.Header().Set("X-XSS-Protection", "1")
|
||||||
|
w.Header().Set("X-Frame-Options", "DENY")
|
||||||
|
|
||||||
|
http.ServeFileFS(w, r, content, "ui/index.html")
|
||||||
|
}
|
||||||
|
func serveStaticContent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("serveStaticContent: %s\n", r.URL.Path)
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/static/style.css":
|
||||||
|
darkTheme := r.CookiesNamed("dark_theme")
|
||||||
|
if len(darkTheme) > 0 && darkTheme[0].Value == "true" {
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/dark.css")
|
||||||
|
} else {
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/light.css")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "/favicon.ico":
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/favicon.ico")
|
||||||
|
break
|
||||||
|
case "/static/index.js":
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/index.js")
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func serveAPI(index *PackageIndex) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveStatus(index *PackageIndex) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if index == nil {
|
||||||
|
http.Error(w, "index is nil", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
base := path.Base(r.URL.Path)
|
||||||
|
name := strings.TrimSuffix(base, ".log")
|
||||||
|
p, ok := rosa.ResolveName(name)
|
||||||
|
if !ok {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m := rosa.GetMetadata(p)
|
||||||
|
pk, ok := index.names[m.Name]
|
||||||
|
if !ok {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(pk.status) > 0 {
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, err := io.Copy(w, bytes.NewReader(pk.status))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SortOrders int
|
||||||
|
|
||||||
|
const (
|
||||||
|
DeclarationAscending SortOrders = iota
|
||||||
|
DeclarationDescending
|
||||||
|
NameAscending
|
||||||
|
NameDescending
|
||||||
|
limitSortOrders
|
||||||
|
)
|
||||||
|
|
||||||
|
type PackageIndex struct {
|
||||||
|
sorts [limitSortOrders][rosa.PresetUnexportedStart]*PackageIndexEntry
|
||||||
|
names map[string]*PackageIndexEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type PackageIndexEntry struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Website string `json:"website"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
status []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPackageIndex(cache *pkg.Cache, report *rosa.Report) (_ *PackageIndex, err error) {
|
||||||
|
index := new(PackageIndex)
|
||||||
|
index.names = make(map[string]*PackageIndexEntry, rosa.PresetUnexportedStart)
|
||||||
|
work := make([]PackageIndexEntry, rosa.PresetUnexportedStart)
|
||||||
|
defer report.HandleAccess(&err)()
|
||||||
|
for p := range rosa.PresetUnexportedStart {
|
||||||
|
m := rosa.GetMetadata(p)
|
||||||
|
v := rosa.Std.Version(p)
|
||||||
|
a := rosa.Std.Load(p)
|
||||||
|
id := cache.Ident(a)
|
||||||
|
st, n := report.ArtifactOf(id)
|
||||||
|
var status []byte
|
||||||
|
if n < 1 {
|
||||||
|
status = nil
|
||||||
|
} else {
|
||||||
|
status = st
|
||||||
|
}
|
||||||
|
log.Printf("Processing package %s...\n", m.Name)
|
||||||
|
entry := PackageIndexEntry{
|
||||||
|
Name: m.Name,
|
||||||
|
Description: m.Description,
|
||||||
|
Website: m.Website,
|
||||||
|
Version: v,
|
||||||
|
status: status,
|
||||||
|
}
|
||||||
|
work[p] = entry
|
||||||
|
index.names[m.Name] = &entry
|
||||||
|
}
|
||||||
|
for i, p := range work {
|
||||||
|
index.sorts[DeclarationAscending][i] = &p
|
||||||
|
}
|
||||||
|
slices.Reverse(work)
|
||||||
|
for i, p := range work {
|
||||||
|
index.sorts[DeclarationDescending][i] = &p
|
||||||
|
}
|
||||||
|
slices.SortFunc(work, func(a PackageIndexEntry, b PackageIndexEntry) int {
|
||||||
|
return cmp.Compare(a.Name, b.Name)
|
||||||
|
})
|
||||||
|
for i, p := range work {
|
||||||
|
index.sorts[NameAscending][i] = &p
|
||||||
|
}
|
||||||
|
slices.Reverse(work)
|
||||||
|
for i, p := range work {
|
||||||
|
index.sorts[NameDescending][i] = &p
|
||||||
|
}
|
||||||
|
return index, err
|
||||||
|
}
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.SetPrefix("pkgserver: ")
|
log.SetPrefix("pkgserver: ")
|
||||||
@@ -35,6 +186,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
log.Println("baseDir:", baseDir)
|
||||||
cache, err := pkg.Open(ctx, msg, 0, baseDir)
|
cache, err := pkg.Open(ctx, msg, 0, baseDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -43,12 +195,19 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
log.Println("reportPath:", reportPath)
|
||||||
|
log.Println("indexing packages...")
|
||||||
index, err := createPackageIndex(cache, report)
|
index, err := createPackageIndex(cache, report)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
uiRoutes()
|
log.Println("created package index")
|
||||||
apiRoutes(index)
|
http.HandleFunc("GET /{$}", serveWebUI)
|
||||||
|
http.HandleFunc("GET /favicon.ico", serveStaticContent)
|
||||||
|
http.HandleFunc("GET /static/", serveStaticContent)
|
||||||
|
http.HandleFunc("GET /api/", serveAPI(index))
|
||||||
|
http.HandleFunc("GET /api/status/", serveStatus(index))
|
||||||
|
log.Println("listening on", flagPort)
|
||||||
err = http.ListenAndServe(fmt.Sprintf(":%d", flagPort), nil)
|
err = http.ListenAndServe(fmt.Sprintf(":%d", flagPort), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate sh -c "sass ui/static/dark.scss ui/static/dark.css && sass ui/static/light.scss ui/static/light.css && tsc ui/static/index.ts"
|
|
||||||
//go:embed ui/*
|
|
||||||
var content embed.FS
|
|
||||||
|
|
||||||
func serveWebUI(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
||||||
w.Header().Set("Pragma", "no-cache")
|
|
||||||
w.Header().Set("Expires", "0")
|
|
||||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
||||||
w.Header().Set("X-XSS-Protection", "1")
|
|
||||||
w.Header().Set("X-Frame-Options", "DENY")
|
|
||||||
|
|
||||||
http.ServeFileFS(w, r, content, "ui/index.html")
|
|
||||||
}
|
|
||||||
func serveStaticContent(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch r.URL.Path {
|
|
||||||
case "/static/style.css":
|
|
||||||
darkTheme := r.CookiesNamed("dark_theme")
|
|
||||||
if len(darkTheme) > 0 && darkTheme[0].Value == "true" {
|
|
||||||
http.ServeFileFS(w, r, content, "ui/static/dark.css")
|
|
||||||
} else {
|
|
||||||
http.ServeFileFS(w, r, content, "ui/static/light.css")
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "/favicon.ico":
|
|
||||||
http.ServeFileFS(w, r, content, "ui/static/favicon.ico")
|
|
||||||
break
|
|
||||||
case "/static/index.js":
|
|
||||||
http.ServeFileFS(w, r, content, "ui/static/index.js")
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
http.NotFound(w, r)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func uiRoutes() {
|
|
||||||
http.HandleFunc("GET /{$}", serveWebUI)
|
|
||||||
http.HandleFunc("GET /favicon.ico", serveStaticContent)
|
|
||||||
http.HandleFunc("GET /static/", serveStaticContent)
|
|
||||||
}
|
|
||||||
@@ -40,7 +40,7 @@ type (
|
|||||||
AllowOrphan bool
|
AllowOrphan bool
|
||||||
// Scheduling policy to set via sched_setscheduler(2). The zero value
|
// Scheduling policy to set via sched_setscheduler(2). The zero value
|
||||||
// skips this call. Supported policies are [SCHED_BATCH], [SCHED_IDLE].
|
// skips this call. Supported policies are [SCHED_BATCH], [SCHED_IDLE].
|
||||||
SchedPolicy SchedPolicy
|
SchedPolicy int
|
||||||
// Cgroup fd, nil to disable.
|
// Cgroup fd, nil to disable.
|
||||||
Cgroup *int
|
Cgroup *int
|
||||||
// ExtraFiles passed through to initial process in the container, with
|
// ExtraFiles passed through to initial process in the container, with
|
||||||
@@ -373,23 +373,12 @@ func (p *Container) Start() error {
|
|||||||
|
|
||||||
// sched_setscheduler: thread-directed but acts on all processes
|
// sched_setscheduler: thread-directed but acts on all processes
|
||||||
// created from the calling thread
|
// created from the calling thread
|
||||||
if p.SchedPolicy > 0 && p.SchedPolicy <= _SCHED_LAST {
|
if p.SchedPolicy > 0 {
|
||||||
var param schedParam
|
|
||||||
if priority, err := p.SchedPolicy.GetPriorityMin(); err != nil {
|
|
||||||
return &StartError{
|
|
||||||
Fatal: true,
|
|
||||||
Step: "get minimum priority",
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
param.priority = priority
|
|
||||||
}
|
|
||||||
|
|
||||||
p.msg.Verbosef("setting scheduling policy %d", p.SchedPolicy)
|
p.msg.Verbosef("setting scheduling policy %d", p.SchedPolicy)
|
||||||
if err := schedSetscheduler(
|
if err := schedSetscheduler(
|
||||||
0, // calling thread
|
0, // calling thread
|
||||||
p.SchedPolicy,
|
p.SchedPolicy,
|
||||||
¶m,
|
&schedParam{0},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return &StartError{
|
return &StartError{
|
||||||
Fatal: true,
|
Fatal: true,
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
. "syscall"
|
. "syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -46,132 +43,18 @@ func Isatty(fd int) bool {
|
|||||||
return r == 0
|
return r == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SchedPolicy denotes a scheduling policy defined in include/uapi/linux/sched.h.
|
|
||||||
type SchedPolicy int
|
|
||||||
|
|
||||||
// include/uapi/linux/sched.h
|
// include/uapi/linux/sched.h
|
||||||
const (
|
const (
|
||||||
SCHED_NORMAL SchedPolicy = iota
|
SCHED_NORMAL = iota
|
||||||
SCHED_FIFO
|
SCHED_FIFO
|
||||||
SCHED_RR
|
SCHED_RR
|
||||||
SCHED_BATCH
|
SCHED_BATCH
|
||||||
_SCHED_ISO // SCHED_ISO: reserved but not implemented yet
|
_ // SCHED_ISO: reserved but not implemented yet
|
||||||
SCHED_IDLE
|
SCHED_IDLE
|
||||||
SCHED_DEADLINE
|
SCHED_DEADLINE
|
||||||
SCHED_EXT
|
SCHED_EXT
|
||||||
|
|
||||||
_SCHED_LAST SchedPolicy = iota - 1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ encoding.TextMarshaler = _SCHED_LAST
|
|
||||||
var _ encoding.TextUnmarshaler = new(_SCHED_LAST)
|
|
||||||
|
|
||||||
// String returns a unique representation of policy, also used in encoding.
|
|
||||||
func (policy SchedPolicy) String() string {
|
|
||||||
switch policy {
|
|
||||||
case SCHED_NORMAL:
|
|
||||||
return ""
|
|
||||||
case SCHED_FIFO:
|
|
||||||
return "fifo"
|
|
||||||
case SCHED_RR:
|
|
||||||
return "rr"
|
|
||||||
case SCHED_BATCH:
|
|
||||||
return "batch"
|
|
||||||
case SCHED_IDLE:
|
|
||||||
return "idle"
|
|
||||||
case SCHED_DEADLINE:
|
|
||||||
return "deadline"
|
|
||||||
case SCHED_EXT:
|
|
||||||
return "ext"
|
|
||||||
|
|
||||||
default:
|
|
||||||
return "invalid policy " + strconv.Itoa(int(policy))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalText performs bounds checking and returns the result of String.
|
|
||||||
func (policy SchedPolicy) MarshalText() ([]byte, error) {
|
|
||||||
if policy == _SCHED_ISO || policy < 0 || policy > _SCHED_LAST {
|
|
||||||
return nil, EINVAL
|
|
||||||
}
|
|
||||||
return []byte(policy.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidSchedPolicyError is an invalid string representation of a [SchedPolicy].
|
|
||||||
type InvalidSchedPolicyError string
|
|
||||||
|
|
||||||
func (InvalidSchedPolicyError) Unwrap() error { return EINVAL }
|
|
||||||
func (e InvalidSchedPolicyError) Error() string {
|
|
||||||
return "invalid scheduling policy " + strconv.Quote(string(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText is the inverse of MarshalText.
|
|
||||||
func (policy *SchedPolicy) UnmarshalText(text []byte) error {
|
|
||||||
switch string(text) {
|
|
||||||
case "fifo":
|
|
||||||
*policy = SCHED_FIFO
|
|
||||||
case "rr":
|
|
||||||
*policy = SCHED_RR
|
|
||||||
case "batch":
|
|
||||||
*policy = SCHED_BATCH
|
|
||||||
case "idle":
|
|
||||||
*policy = SCHED_IDLE
|
|
||||||
case "deadline":
|
|
||||||
*policy = SCHED_DEADLINE
|
|
||||||
case "ext":
|
|
||||||
*policy = SCHED_EXT
|
|
||||||
|
|
||||||
case "":
|
|
||||||
*policy = 0
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return InvalidSchedPolicyError(text)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// for sched_get_priority_max and sched_get_priority_min
|
|
||||||
var (
|
|
||||||
schedPriority [_SCHED_LAST + 1][2]std.Int
|
|
||||||
schedPriorityErr [_SCHED_LAST + 1][2]error
|
|
||||||
schedPriorityOnce [_SCHED_LAST + 1][2]sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetPriorityMax returns the maximum priority value that can be used with the
|
|
||||||
// scheduling algorithm identified by policy.
|
|
||||||
func (policy SchedPolicy) GetPriorityMax() (std.Int, error) {
|
|
||||||
schedPriorityOnce[policy][0].Do(func() {
|
|
||||||
priority, _, errno := Syscall(
|
|
||||||
SYS_SCHED_GET_PRIORITY_MAX,
|
|
||||||
uintptr(policy),
|
|
||||||
0, 0,
|
|
||||||
)
|
|
||||||
schedPriority[policy][0] = std.Int(priority)
|
|
||||||
if schedPriority[policy][0] < 0 {
|
|
||||||
schedPriorityErr[policy][0] = errno
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return schedPriority[policy][0], schedPriorityErr[policy][0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPriorityMin returns the minimum priority value that can be used with the
|
|
||||||
// scheduling algorithm identified by policy.
|
|
||||||
func (policy SchedPolicy) GetPriorityMin() (std.Int, error) {
|
|
||||||
schedPriorityOnce[policy][1].Do(func() {
|
|
||||||
priority, _, errno := Syscall(
|
|
||||||
SYS_SCHED_GET_PRIORITY_MIN,
|
|
||||||
uintptr(policy),
|
|
||||||
0, 0,
|
|
||||||
)
|
|
||||||
schedPriority[policy][1] = std.Int(priority)
|
|
||||||
if schedPriority[policy][1] < 0 {
|
|
||||||
schedPriorityErr[policy][1] = errno
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return schedPriority[policy][1], schedPriorityErr[policy][1]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// schedParam is equivalent to struct sched_param from include/linux/sched.h.
|
// schedParam is equivalent to struct sched_param from include/linux/sched.h.
|
||||||
type schedParam struct {
|
type schedParam struct {
|
||||||
// sched_priority
|
// sched_priority
|
||||||
@@ -191,7 +74,7 @@ type schedParam struct {
|
|||||||
// this if you do not have something similar in place!
|
// this if you do not have something similar in place!
|
||||||
//
|
//
|
||||||
// [very subtle to use correctly]: https://www.openwall.com/lists/musl/2016/03/01/4
|
// [very subtle to use correctly]: https://www.openwall.com/lists/musl/2016/03/01/4
|
||||||
func schedSetscheduler(tid int, policy SchedPolicy, param *schedParam) error {
|
func schedSetscheduler(tid, policy int, param *schedParam) error {
|
||||||
if r, _, errno := Syscall(
|
if r, _, errno := Syscall(
|
||||||
SYS_SCHED_SETSCHEDULER,
|
SYS_SCHED_SETSCHEDULER,
|
||||||
uintptr(tid),
|
uintptr(tid),
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
package container_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/container"
|
|
||||||
"hakurei.app/container/std"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSchedPolicyJSON(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
policy container.SchedPolicy
|
|
||||||
want string
|
|
||||||
encodeErr error
|
|
||||||
decodeErr error
|
|
||||||
}{
|
|
||||||
{container.SCHED_NORMAL, `""`, nil, nil},
|
|
||||||
{container.SCHED_FIFO, `"fifo"`, nil, nil},
|
|
||||||
{container.SCHED_RR, `"rr"`, nil, nil},
|
|
||||||
{container.SCHED_BATCH, `"batch"`, nil, nil},
|
|
||||||
{4, `"invalid policy 4"`, syscall.EINVAL, container.InvalidSchedPolicyError("invalid policy 4")},
|
|
||||||
{container.SCHED_IDLE, `"idle"`, nil, nil},
|
|
||||||
{container.SCHED_DEADLINE, `"deadline"`, nil, nil},
|
|
||||||
{container.SCHED_EXT, `"ext"`, nil, nil},
|
|
||||||
{math.MaxInt, `"iso"`, syscall.EINVAL, container.InvalidSchedPolicyError("iso")},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
name := tc.policy.String()
|
|
||||||
if tc.policy == container.SCHED_NORMAL {
|
|
||||||
name = "normal"
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
got, err := json.Marshal(tc.policy)
|
|
||||||
if !errors.Is(err, tc.encodeErr) {
|
|
||||||
t.Fatalf("Marshal: error = %v, want %v", err, tc.encodeErr)
|
|
||||||
}
|
|
||||||
if err == nil && string(got) != tc.want {
|
|
||||||
t.Fatalf("Marshal: %s, want %s", string(got), tc.want)
|
|
||||||
}
|
|
||||||
|
|
||||||
var v container.SchedPolicy
|
|
||||||
if err = json.Unmarshal([]byte(tc.want), &v); !reflect.DeepEqual(err, tc.decodeErr) {
|
|
||||||
t.Fatalf("Unmarshal: error = %v, want %v", err, tc.decodeErr)
|
|
||||||
}
|
|
||||||
if err == nil && v != tc.policy {
|
|
||||||
t.Fatalf("Unmarshal: %d, want %d", v, tc.policy)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSchedPolicyMinMax(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
policy container.SchedPolicy
|
|
||||||
min, max std.Int
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{container.SCHED_NORMAL, 0, 0, nil},
|
|
||||||
{container.SCHED_FIFO, 1, 99, nil},
|
|
||||||
{container.SCHED_RR, 1, 99, nil},
|
|
||||||
{container.SCHED_BATCH, 0, 0, nil},
|
|
||||||
{4, -1, -1, syscall.EINVAL},
|
|
||||||
{container.SCHED_IDLE, 0, 0, nil},
|
|
||||||
{container.SCHED_DEADLINE, 0, 0, nil},
|
|
||||||
{container.SCHED_EXT, 0, 0, nil},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
name := tc.policy.String()
|
|
||||||
if tc.policy == container.SCHED_NORMAL {
|
|
||||||
name = "normal"
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if priority, err := tc.policy.GetPriorityMax(); !reflect.DeepEqual(err, tc.err) {
|
|
||||||
t.Fatalf("GetPriorityMax: error = %v, want %v", err, tc.err)
|
|
||||||
} else if priority != tc.max {
|
|
||||||
t.Fatalf("GetPriorityMax: %d, want %d", priority, tc.max)
|
|
||||||
}
|
|
||||||
if priority, err := tc.policy.GetPriorityMin(); !reflect.DeepEqual(err, tc.err) {
|
|
||||||
t.Fatalf("GetPriorityMin: error = %v, want %v", err, tc.err)
|
|
||||||
} else if priority != tc.min {
|
|
||||||
t.Fatalf("GetPriorityMin: %d, want %d", priority, tc.min)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
flake.lock
generated
12
flake.lock
generated
@@ -7,11 +7,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1772985280,
|
"lastModified": 1765384171,
|
||||||
"narHash": "sha256-FdrNykOoY9VStevU4zjSUdvsL9SzJTcXt4omdEDZDLk=",
|
"narHash": "sha256-FuFtkJrW1Z7u+3lhzPRau69E0CNjADku1mLQQflUORo=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "8f736f007139d7f70752657dff6a401a585d6cbc",
|
"rev": "44777152652bc9eacf8876976fa72cc77ca8b9d8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -23,11 +23,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1772822230,
|
"lastModified": 1765311797,
|
||||||
"narHash": "sha256-yf3iYLGbGVlIthlQIk5/4/EQDZNNEmuqKZkQssMljuw=",
|
"narHash": "sha256-mSD5Ob7a+T2RNjvPvOA1dkJHGVrNVl8ZOrAwBjKBDQo=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "71caefce12ba78d84fe618cf61644dce01cf3a96",
|
"rev": "09eb77e94fa25202af8f3e81ddc7353d9970ac1b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
hakurei = pkgs.pkgsStatic.callPackage ./package.nix {
|
hakurei = pkgs.pkgsStatic.callPackage ./package.nix {
|
||||||
inherit (pkgs)
|
inherit (pkgs)
|
||||||
# passthru.buildInputs
|
# passthru.buildInputs
|
||||||
go_1_26
|
go
|
||||||
clang
|
clang
|
||||||
|
|
||||||
# nativeBuildInputs
|
# nativeBuildInputs
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
let
|
let
|
||||||
# this is used for interactive vm testing during development, where tests might be broken
|
# this is used for interactive vm testing during development, where tests might be broken
|
||||||
package = self.packages.${pkgs.stdenv.hostPlatform.system}.hakurei.override {
|
package = self.packages.${pkgs.stdenv.hostPlatform.system}.hakurei.override {
|
||||||
buildGo126Module = previousArgs: pkgs.pkgsStatic.buildGo126Module (previousArgs // { doCheck = false; });
|
buildGoModule = previousArgs: pkgs.pkgsStatic.buildGoModule (previousArgs // { doCheck = false; });
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ type ExecPath struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SchedPolicy is the [container] scheduling policy.
|
// SchedPolicy is the [container] scheduling policy.
|
||||||
var SchedPolicy container.SchedPolicy
|
var SchedPolicy int
|
||||||
|
|
||||||
// PromoteLayers returns artifacts with identical-by-content layers promoted to
|
// PromoteLayers returns artifacts with identical-by-content layers promoted to
|
||||||
// the highest priority instance, as if mounted via [ExecPath].
|
// the highest priority instance, as if mounted via [ExecPath].
|
||||||
|
|||||||
@@ -82,11 +82,6 @@ install -Dm0500 \
|
|||||||
echo "Installing linux $1..."
|
echo "Installing linux $1..."
|
||||||
cp -av "$2" "$4"
|
cp -av "$2" "$4"
|
||||||
cp -av "$3" "$4"
|
cp -av "$3" "$4"
|
||||||
`))),
|
|
||||||
pkg.Path(AbsUsrSrc.Append(
|
|
||||||
".depmod",
|
|
||||||
), false, pkg.NewFile("depmod", []byte(`#!/bin/sh
|
|
||||||
exec /system/sbin/depmod -m /lib/modules "$@"
|
|
||||||
`))),
|
`))),
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1215,11 +1210,6 @@ cgit 1.2.3-korg
|
|||||||
"all",
|
"all",
|
||||||
},
|
},
|
||||||
Install: `
|
Install: `
|
||||||
# kernel is not aware of kmod moduledir
|
|
||||||
install -Dm0500 \
|
|
||||||
/usr/src/.depmod \
|
|
||||||
/sbin/depmod
|
|
||||||
|
|
||||||
make \
|
make \
|
||||||
"-j$(nproc)" \
|
"-j$(nproc)" \
|
||||||
-f /usr/src/kernel/Makefile \
|
-f /usr/src/kernel/Makefile \
|
||||||
@@ -1227,10 +1217,9 @@ make \
|
|||||||
LLVM=1 \
|
LLVM=1 \
|
||||||
INSTALL_PATH=/work \
|
INSTALL_PATH=/work \
|
||||||
install \
|
install \
|
||||||
INSTALL_MOD_PATH=/work/system \
|
INSTALL_MOD_PATH=/work \
|
||||||
DEPMOD=/sbin/depmod \
|
|
||||||
modules_install
|
modules_install
|
||||||
rm -v /work/system/lib/modules/` + kernelVersion + `/build
|
rm -v /work/lib/modules/` + kernelVersion + `/build
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
Flex,
|
Flex,
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ func (t Toolchain) newKmod() (pkg.Artifact, string) {
|
|||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), nil, &MesonHelper{
|
), nil, &MesonHelper{
|
||||||
Setup: [][2]string{
|
Setup: [][2]string{
|
||||||
{"Dmoduledir", "/system/lib/modules"},
|
|
||||||
{"Dsysconfdir", "/system/etc"},
|
{"Dsysconfdir", "/system/etc"},
|
||||||
{"Dbashcompletiondir", "no"},
|
{"Dbashcompletiondir", "no"},
|
||||||
{"Dfishcompletiondir", "no"},
|
{"Dfishcompletiondir", "no"},
|
||||||
|
|||||||
@@ -125,8 +125,6 @@ func (t Toolchain) newLLVMVariant(variant string, attr *llvmAttr) pkg.Artifact {
|
|||||||
|
|
||||||
[2]string{"LLVM_INSTALL_BINUTILS_SYMLINKS", "ON"},
|
[2]string{"LLVM_INSTALL_BINUTILS_SYMLINKS", "ON"},
|
||||||
[2]string{"LLVM_INSTALL_CCTOOLS_SYMLINKS", "ON"},
|
[2]string{"LLVM_INSTALL_CCTOOLS_SYMLINKS", "ON"},
|
||||||
|
|
||||||
[2]string{"LLVM_LIT_ARGS", "'--verbose'"},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
package.nix
16
package.nix
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
stdenv,
|
stdenv,
|
||||||
buildGo126Module,
|
buildGoModule,
|
||||||
makeBinaryWrapper,
|
makeBinaryWrapper,
|
||||||
xdg-dbus-proxy,
|
xdg-dbus-proxy,
|
||||||
pkg-config,
|
pkg-config,
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
fuse3,
|
fuse3,
|
||||||
|
|
||||||
# for passthru.buildInputs
|
# for passthru.buildInputs
|
||||||
go_1_26,
|
go,
|
||||||
clang,
|
clang,
|
||||||
|
|
||||||
# for check
|
# for check
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
withStatic ? stdenv.hostPlatform.isStatic,
|
withStatic ? stdenv.hostPlatform.isStatic,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
buildGo126Module rec {
|
buildGoModule rec {
|
||||||
pname = "hakurei";
|
pname = "hakurei";
|
||||||
version = "0.3.6";
|
version = "0.3.6";
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ buildGo126Module rec {
|
|||||||
];
|
];
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
go_1_26
|
go
|
||||||
pkg-config
|
pkg-config
|
||||||
wayland-scanner
|
wayland-scanner
|
||||||
];
|
];
|
||||||
@@ -125,11 +125,8 @@ buildGo126Module rec {
|
|||||||
--inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages}
|
--inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
passthru = {
|
passthru.targetPkgs = [
|
||||||
go = go_1_26;
|
go
|
||||||
|
|
||||||
targetPkgs = [
|
|
||||||
go_1_26
|
|
||||||
clang
|
clang
|
||||||
xorg.xorgproto
|
xorg.xorgproto
|
||||||
util-linux
|
util-linux
|
||||||
@@ -140,5 +137,4 @@ buildGo126Module rec {
|
|||||||
]
|
]
|
||||||
++ buildInputs
|
++ buildInputs
|
||||||
++ nativeBuildInputs;
|
++ nativeBuildInputs;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ testers.nixosTest {
|
|||||||
(writeShellScriptBin "hakurei-test" ''
|
(writeShellScriptBin "hakurei-test" ''
|
||||||
# Assert hst CGO_ENABLED=0: ${
|
# Assert hst CGO_ENABLED=0: ${
|
||||||
with pkgs;
|
with pkgs;
|
||||||
runCommand "hakurei-hst-cgo" { nativeBuildInputs = [ self.packages.${system}.hakurei.go ]; } ''
|
runCommand "hakurei-hst-cgo" { nativeBuildInputs = [ go ]; } ''
|
||||||
cp -r ${options.environment.hakurei.package.default.src} "$out"
|
cp -r ${options.environment.hakurei.package.default.src} "$out"
|
||||||
chmod -R +w "$out"
|
chmod -R +w "$out"
|
||||||
cp ${writeText "hst_cgo_test.go" ''package hakurei_test;import("testing";"hakurei.app/hst");func TestTemplate(t *testing.T){hst.Template()}''} "$out/hst_cgo_test.go"
|
cp ${writeText "hst_cgo_test.go" ''package hakurei_test;import("testing";"hakurei.app/hst");func TestTemplate(t *testing.T){hst.Template()}''} "$out/hst_cgo_test.go"
|
||||||
|
|||||||
Reference in New Issue
Block a user