-
Notifications
You must be signed in to change notification settings - Fork 619
Expand file tree
/
Copy pathversionsprocessor.go
More file actions
225 lines (182 loc) · 7.4 KB
/
versionsprocessor.go
File metadata and controls
225 lines (182 loc) · 7.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// versionsprocessor is a tool to generate a macro file of all specs version and release.
// It iterates over all of the SPEC files in the provided directory, gets the version and release for each SPEC,
// and then writes that information to an output file as RPM macros file.
// The output file is then provided to other tools and passed into their respective rpm macros folder so that rpmbuild command automatically recognize it.
package main
import (
"fmt"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"github.com/microsoft/azurelinux/toolkit/tools/internal/exe"
"github.com/microsoft/azurelinux/toolkit/tools/internal/file"
"github.com/microsoft/azurelinux/toolkit/tools/internal/logger"
"github.com/microsoft/azurelinux/toolkit/tools/internal/rpm"
"github.com/microsoft/azurelinux/toolkit/tools/internal/safechroot"
"github.com/microsoft/azurelinux/toolkit/tools/internal/timestamp"
"github.com/microsoft/azurelinux/toolkit/tools/pkg/profile"
"github.com/microsoft/azurelinux/toolkit/tools/pkg/specreaderutils"
"gopkg.in/alecthomas/kingpin.v2"
)
var (
app = kingpin.New("versionsprocessor", "A tool to generate a macro file of all specs version and release")
specsDir = exe.InputDirFlag(app, "Directory to scan for SPECS")
output = exe.OutputFlag(app, "Output file to export the JSON")
distTag = app.Flag("dist-tag", "The distribution tag the SPEC will be built with.").Required().String()
targetArch = app.Flag("target-arch", "The architecture of the machine the RPM binaries run on").String()
buildDir = app.Flag("build-dir", "Directory to store temporary files while parsing.").String()
logFlags = exe.SetupLogFlags(app)
profFlags = exe.SetupProfileFlags(app)
workerTar = app.Flag("worker-tar", "Full path to worker_chroot.tar.gz. If this argument is empty, specs will be parsed in the host environment.").ExistingFile()
timestampFile = app.Flag("timestamp-file", "File that stores timestamps for this program.").String()
extraFiles = app.Flag("extra-macros-file", "Additional files whose contents will be appended to the output; may be specified multiple times.").ExistingFiles()
)
func main() {
var (
chroot *safechroot.Chroot
macrosOutput []string
)
app.Version(exe.ToolkitVersion)
kingpin.MustParse(app.Parse(os.Args[1:]))
logger.InitBestEffort(logFlags)
prof, err := profile.StartProfiling(profFlags)
if err != nil {
logger.Log.Warnf("Could not start profiling: %s", err)
}
defer prof.StopProfiler()
timestamp.BeginTiming("versionsprocessor", *timestampFile)
defer timestamp.CompleteTiming()
var buildArch string = *targetArch
if *targetArch == "" {
buildArch, err = rpm.GetRpmArch(runtime.GOARCH)
if err != nil {
logger.Log.Errorf("Failed to get RPM architecture for GOARCH %s: %s", runtime.GOARCH, err)
return
}
}
if *workerTar == "" {
logger.Log.Error("No worker tar provided, please provide a worker tar to parse specs in the chroot environment.")
return
}
const leaveFilesOnDisk = false
chroot, err = specreaderutils.CreateChroot("versionprocessor_chroot", *workerTar, *buildDir, *specsDir)
if err != nil {
logger.PanicOnError("Failed to create chroot")
}
defer chroot.Close(leaveFilesOnDisk)
doParse := func() error {
// Find all spec files in provided specs dir
allSpecFiles, err := specreaderutils.FindSpecFiles(*specsDir, nil)
if err != nil {
logger.Log.Errorf("Error finding spec files: %s", err)
return err
}
logger.Log.Infof("Processing version and release for %d spec files into %s", len(allSpecFiles), *output)
type ProcessResult struct {
macros []string
err error
}
resultsChannel := make(chan ProcessResult, len(allSpecFiles))
for _, specFile := range allSpecFiles {
go func(sf string) {
macros, processErr := processSpecFile(sf, buildArch, *distTag, nil)
if processErr != nil {
processErr = fmt.Errorf("error processing spec file (%s): %w", sf, processErr)
}
resultsChannel <- ProcessResult{
macros: macros,
err: processErr,
}
}(specFile)
}
for i := 0; i < len(allSpecFiles); i++ {
result := <-resultsChannel
if result.err != nil {
logger.Log.Errorf("%s", result.err)
return result.err
}
macrosOutput = append(macrosOutput, result.macros...)
}
return err
}
err = chroot.Run(doParse)
if err != nil {
logger.Log.Fatalf("Error processing spec files: %s", err)
}
err = writeExtraFilesToOutput(*extraFiles, macrosOutput, *output)
if err != nil {
logger.Log.Errorf("Failed to write extra files to output: %s", err)
os.Exit(1)
}
}
func processSpecFile(specFile string, buildArch string, distTag string, macrosOutput []string) (newMacrosOutput []string, err error) {
specFileName := filepath.Base(specFile)
sourceDir := filepath.Dir(specFile)
defines := rpm.DefaultDistroDefines(false, distTag)
packages, err := rpm.QuerySPECForBuiltRPMs(specFile, sourceDir, buildArch, defines)
if err != nil {
logger.Log.Errorf("Failed to query spec file (%s). Error: %s", specFileName, err)
return nil, err
}
for _, packageNEVRA := range packages {
macros, processErr := processPackageVersionString(packageNEVRA)
if processErr != nil {
logger.Log.Errorf("Failed to process package (%s) from spec file (%s): %s", packageNEVRA, specFileName, processErr)
continue
}
macrosOutput = append(macrosOutput, macros...)
}
return macrosOutput, nil
}
func processPackageVersionString(packageNEVRA string) (macros []string, err error) {
const prefix = "azl"
matches := rpm.RpmSpecBuiltRPMRegex.FindStringSubmatch(packageNEVRA)
if len(matches) != rpm.RpmSpecBuiltRPMRegexMatchesCount {
return nil, fmt.Errorf("invalid package NEVRA format: %q", packageNEVRA)
}
name := matches[rpm.RpmSpecBuiltRPMRegexNameIndex]
epoch := matches[rpm.RpmSpecBuiltRPMRegexEpochIndex]
version := matches[rpm.RpmSpecBuiltRPMRegexVersionIndex]
release := matches[rpm.RpmSpecBuiltRPMRegexReleaseIndex]
// Replace '-' with '_' as RPM macros cannot have '-'.
nameMacroFormat := strings.ReplaceAll(name, "-", "_")
epochMacroString := prefix + "_" + nameMacroFormat + "_epoch"
versionMacroString := prefix + "_" + nameMacroFormat + "_version"
releaseMacroString := prefix + "_" + nameMacroFormat + "_release"
// Generate RPM macro definitions instead of modifying spec files directly.
macros = []string{
fmt.Sprintf("%%%s %s", versionMacroString, version),
fmt.Sprintf("%%%s %s", releaseMacroString, release),
}
// Only append (to the front of the list) if we have an epoch
if epoch != "" {
macros = append([]string{fmt.Sprintf("%%%s %s", epochMacroString, epoch)}, macros...)
}
return macros, nil
}
func writeExtraFilesToOutput(extraFiles []string, macrosOutput []string, output string) (err error) {
// If extra files were provided, append their contents to the output as well.
for _, extraPath := range extraFiles {
if strings.TrimSpace(extraPath) == "" {
continue
}
contents, readErr := file.ReadLines(extraPath)
if readErr != nil {
logger.Log.Errorf("Failed to read extra macros file (%s): %s", extraPath, readErr)
continue
}
macrosOutput = append(macrosOutput, contents...)
logger.Log.Infof("Appended contents of provided extra macros file (%s) to %s", extraPath, output)
}
sort.Strings(macrosOutput)
err = file.WriteLines(macrosOutput, output)
if err != nil {
logger.Log.Errorf("Failed to write file (%s)", output)
return err
}
return nil
}