blob: 87d6e099b31557e4baf9deed09bccacc38985c8d [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 '-'")
Jan Schärd4309bb2025-07-18 10:13:22 +020034 flagPlatformOS = flag.String("platform_os", "", "platform OS")
Jan Schäre6c0c322025-05-12 16:14:25 +000035)
36
37var (
38 rePrereleaseGitHash = regexp.MustCompile(`^g[0-9a-f]+$`)
39)
40
41func versionWithoutGitInfo(version string) string {
42 version, metadata, hasMetadata := strings.Cut(version, "+")
43 version, prerelease, hasPrerelease := strings.Cut(version, "-")
44 if hasPrerelease {
45 var filteredParts []string
46 for part := range strings.SplitSeq(prerelease, ".") {
47 switch {
48 case part == "dirty":
49 // Ignore field.
50 case rePrereleaseGitHash.FindStringSubmatch(part) != nil:
51 // Ignore field.
52 default:
53 filteredParts = append(filteredParts, part)
54 }
55 }
56 if len(filteredParts) != 0 {
57 version = version + "-" + strings.Join(filteredParts, ".")
58 }
59 }
60 if hasMetadata {
61 version = version + "+" + metadata
62 }
63 return version
64}
65
66func main() {
67 var componentIDs []string
68 flag.Func("component", "ID of a component", func(component string) error {
69 componentIDs = append(componentIDs, component)
70 return nil
71 })
72 flag.Parse()
73
74 statusFileContent, err := os.ReadFile(*flagStatusFile)
75 if err != nil {
76 log.Fatalf("Failed to open bazel workspace status file: %v", err)
77 }
78 statusVars := make(map[string]string)
79 for line := range strings.SplitSeq(string(statusFileContent), "\n") {
80 if line == "" {
81 continue
82 }
83 key, value, ok := strings.Cut(line, " ")
84 if !ok {
85 log.Fatalf("Invalid line in status file: %q", line)
86 }
87 statusVars[key] = value
88 }
89
90 version, ok := statusVars[*flagStampVar]
91 if !ok {
92 log.Fatalf("%s key not set in bazel workspace status file", *flagStampVar)
93 }
94
95 var components []osimage.Component
96 for _, id := range componentIDs {
97 versionKey := fmt.Sprintf("STABLE_MONOGON_componentVersion_%s", id)
98 version, ok := statusVars[versionKey]
99 if !ok {
100 log.Fatalf("%s key not set in bazel workspace status file", versionKey)
101 }
102 components = append(components, osimage.Component{
103 ID: id,
104 Version: version,
105 })
106 }
107
108 variant := *flagArchitecture
109 if *flagBuildFlags != "" {
110 variant = variant + "-" + *flagBuildFlags
111 }
112
113 productInfo := osimage.ProductInfo{
114 ID: *flagID,
115 Name: *flagName,
116 Version: versionWithoutGitInfo(version),
117 Variant: variant,
118 CommitHash: statusVars["STABLE_MONOGON_gitCommit"],
119 CommitDate: statusVars["STABLE_MONOGON_gitCommitDate"],
120 BuildTreeDirty: statusVars["STABLE_MONOGON_gitTreeState"] == "dirty",
Jan Schärd4309bb2025-07-18 10:13:22 +0200121 PlatformOS: *flagPlatformOS,
Jan Schäre6c0c322025-05-12 16:14:25 +0000122 Components: components,
123 }
124 productInfoBytes, err := json.MarshalIndent(productInfo, "", "\t")
125 if err != nil {
126 log.Fatalf("Failed to marshal OS image config: %v", err)
127 }
128 productInfoBytes = append(productInfoBytes, '\n')
129 if err := os.WriteFile(*flagProductInfoFile, productInfoBytes, 0644); err != nil {
130 log.Fatalf("Failed to write product info file: %v", err)
131 }
132
133 // As specified by https://www.freedesktop.org/software/systemd/man/os-release.html
134 osReleaseVars := map[string]string{
135 "NAME": *flagName,
136 "ID": *flagID,
137 "VERSION": version,
138 "VERSION_ID": version,
139 "PRETTY_NAME": *flagName + " " + version,
140 }
141 osReleaseContent, err := godotenv.Marshal(osReleaseVars)
142 if err != nil {
143 log.Fatalf("Failed to encode os-release file: %v", err)
144 }
145 if err := os.WriteFile(*flagOSReleaseFile, []byte(osReleaseContent+"\n"), 0644); err != nil {
146 log.Fatalf("Failed to write os-release file: %v", err)
147 }
148}