blob: e4b566d560eaced73b53a9638ce99fc36ba64b57 [file] [log] [blame]
Jan Schäre6c0c322025-05-12 16:14:25 +00001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
4// genproductinfo generates a product info JSON file from arguments and
5// stamping. Additionally, it generates an os-release file following the
6// freedesktop spec from the same information.
7//
8// https://www.freedesktop.org/software/systemd/man/os-release.html
9package main
10
11import (
12 "encoding/json"
13 "flag"
14 "fmt"
15 "log"
16 "os"
17 "regexp"
18 "strings"
19
20 "github.com/joho/godotenv"
21
22 "source.monogon.dev/osbase/oci/osimage"
23)
24
25var (
26 flagStatusFile = flag.String("status_file", "", "path to bazel workspace status file")
27 flagProductInfoFile = flag.String("product_info_file", "product-info", "path to product info output file")
28 flagOSReleaseFile = flag.String("os_release_file", "os-release", "path to os-release output file")
29 flagStampVar = flag.String("stamp_var", "", "variable to use as version from the workspace status file")
30 flagName = flag.String("name", "", "name parameter (see freedesktop spec)")
31 flagID = flag.String("id", "", "id parameter (see freedesktop spec)")
32 flagArchitecture = flag.String("architecture", "", "CPU architecture")
33 flagBuildFlags = flag.String("build_flags", "", "build flags joined by '-'")
34)
35
36var (
37 rePrereleaseGitHash = regexp.MustCompile(`^g[0-9a-f]+$`)
38)
39
40func versionWithoutGitInfo(version string) string {
41 version, metadata, hasMetadata := strings.Cut(version, "+")
42 version, prerelease, hasPrerelease := strings.Cut(version, "-")
43 if hasPrerelease {
44 var filteredParts []string
45 for part := range strings.SplitSeq(prerelease, ".") {
46 switch {
47 case part == "dirty":
48 // Ignore field.
49 case rePrereleaseGitHash.FindStringSubmatch(part) != nil:
50 // Ignore field.
51 default:
52 filteredParts = append(filteredParts, part)
53 }
54 }
55 if len(filteredParts) != 0 {
56 version = version + "-" + strings.Join(filteredParts, ".")
57 }
58 }
59 if hasMetadata {
60 version = version + "+" + metadata
61 }
62 return version
63}
64
65func main() {
66 var componentIDs []string
67 flag.Func("component", "ID of a component", func(component string) error {
68 componentIDs = append(componentIDs, component)
69 return nil
70 })
71 flag.Parse()
72
73 statusFileContent, err := os.ReadFile(*flagStatusFile)
74 if err != nil {
75 log.Fatalf("Failed to open bazel workspace status file: %v", err)
76 }
77 statusVars := make(map[string]string)
78 for line := range strings.SplitSeq(string(statusFileContent), "\n") {
79 if line == "" {
80 continue
81 }
82 key, value, ok := strings.Cut(line, " ")
83 if !ok {
84 log.Fatalf("Invalid line in status file: %q", line)
85 }
86 statusVars[key] = value
87 }
88
89 version, ok := statusVars[*flagStampVar]
90 if !ok {
91 log.Fatalf("%s key not set in bazel workspace status file", *flagStampVar)
92 }
93
94 var components []osimage.Component
95 for _, id := range componentIDs {
96 versionKey := fmt.Sprintf("STABLE_MONOGON_componentVersion_%s", id)
97 version, ok := statusVars[versionKey]
98 if !ok {
99 log.Fatalf("%s key not set in bazel workspace status file", versionKey)
100 }
101 components = append(components, osimage.Component{
102 ID: id,
103 Version: version,
104 })
105 }
106
107 variant := *flagArchitecture
108 if *flagBuildFlags != "" {
109 variant = variant + "-" + *flagBuildFlags
110 }
111
112 productInfo := osimage.ProductInfo{
113 ID: *flagID,
114 Name: *flagName,
115 Version: versionWithoutGitInfo(version),
116 Variant: variant,
117 CommitHash: statusVars["STABLE_MONOGON_gitCommit"],
118 CommitDate: statusVars["STABLE_MONOGON_gitCommitDate"],
119 BuildTreeDirty: statusVars["STABLE_MONOGON_gitTreeState"] == "dirty",
120 Components: components,
121 }
122 productInfoBytes, err := json.MarshalIndent(productInfo, "", "\t")
123 if err != nil {
124 log.Fatalf("Failed to marshal OS image config: %v", err)
125 }
126 productInfoBytes = append(productInfoBytes, '\n')
127 if err := os.WriteFile(*flagProductInfoFile, productInfoBytes, 0644); err != nil {
128 log.Fatalf("Failed to write product info file: %v", err)
129 }
130
131 // As specified by https://www.freedesktop.org/software/systemd/man/os-release.html
132 osReleaseVars := map[string]string{
133 "NAME": *flagName,
134 "ID": *flagID,
135 "VERSION": version,
136 "VERSION_ID": version,
137 "PRETTY_NAME": *flagName + " " + version,
138 }
139 osReleaseContent, err := godotenv.Marshal(osReleaseVars)
140 if err != nil {
141 log.Fatalf("Failed to encode os-release file: %v", err)
142 }
143 if err := os.WriteFile(*flagOSReleaseFile, []byte(osReleaseContent+"\n"), 0644); err != nil {
144 log.Fatalf("Failed to write os-release file: %v", err)
145 }
146}