-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathpreparesources.go
More file actions
219 lines (173 loc) · 7.24 KB
/
Copy pathpreparesources.go
File metadata and controls
219 lines (173 loc) · 7.24 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
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package component
import (
"errors"
"fmt"
"log/slog"
"github.com/microsoft/azure-linux-dev-tools/internal/app/azldev"
"github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/components"
"github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/sources"
"github.com/microsoft/azure-linux-dev-tools/internal/providers/sourceproviders"
"github.com/microsoft/azure-linux-dev-tools/internal/utils/fileutils"
"github.com/spf13/cobra"
)
type PrepareSourcesOptions struct {
ComponentFilter components.ComponentFilter
OutputDir string
SkipOverlays bool
WithoutGitRepo bool
Force bool
AllowNoHashes bool
SkipSources bool
}
func prepareOnAppInit(_ *azldev.App, sourceCmd *cobra.Command) {
sourceCmd.AddCommand(NewPrepareSourcesCmd())
}
func NewPrepareSourcesCmd() *cobra.Command {
var options PrepareSourcesOptions
cmd := &cobra.Command{
Use: "prepare-sources",
Aliases: []string{"prep-sources"},
Short: "Prepare buildable sources for components",
Long: `Prepare buildable sources for a component by fetching the upstream spec and
source files, then applying any configured overlays.
The result is a directory containing the spec file and all sources, ready
for inspection or manual building. This is useful for verifying that
overlays apply cleanly before running a full build.
Only one component may be selected at a time.`,
Example: ` # Prepare sources for a component
azldev component prep-sources -p curl -o ./build/work/scratch/curl --force
# Prepare without applying overlays (raw upstream sources)
azldev component prep-sources -p curl -o ./build/work/scratch/curl --skip-overlays --force`,
RunE: azldev.RunFuncWithExtraArgs(func(env *azldev.Env, args []string) (interface{}, error) {
options.ComponentFilter.ComponentNamePatterns = append(args, options.ComponentFilter.ComponentNamePatterns...)
return nil, PrepareComponentSources(env, &options)
}),
ValidArgsFunction: components.GenerateComponentNameCompletions,
Annotations: map[string]string{
azldev.CommandAnnotationRootOK: "true",
},
}
components.AddComponentFilterOptionsToCommand(cmd, &options.ComponentFilter)
cmd.Flags().StringVarP(&options.OutputDir, "output-dir", "o", "", "output directory")
_ = cmd.MarkFlagRequired("output-dir")
_ = cmd.MarkFlagDirname("output-dir")
cmd.Flags().BoolVar(&options.SkipOverlays, "skip-overlays", false, "skip applying overlays to prepared sources")
cmd.Flags().BoolVar(&options.WithoutGitRepo, "without-git", false,
"Disable dist-git repository creation (enabled by default)")
cmd.Flags().BoolVar(&options.Force, "force", false, "delete and recreate the output directory if it already exists")
cmd.Flags().BoolVar(&options.AllowNoHashes, "allow-no-hashes", false,
"compute missing hashes by downloading source files from their origin")
cmd.Flags().BoolVar(&options.SkipSources, "skip-sources", false,
"skip downloading fetched sources when preparing the package (useful to extract "+
"dist-git metadata when source files are not needed)")
return cmd
}
func PrepareComponentSources(env *azldev.Env, options *PrepareSourcesOptions) error {
var comps *components.ComponentSet
resolver := components.NewResolver(env)
comps, err := resolver.FindComponents(&options.ComponentFilter)
if err != nil {
return fmt.Errorf("failed to resolve components:\n%w", err)
}
if comps.Len() == 0 {
return errors.New("no components were selected; " +
"please use command-line options to indicate which components you would like to build",
)
}
if comps.Len() != 1 {
return fmt.Errorf("expected exactly one component, got %d", comps.Len())
}
component := comps.Components()[0]
event := env.StartEvent("Preparing sources", "component", component.GetName(), "outputDir", options.OutputDir)
defer event.End()
var sourceManager sourceproviders.SourceManager
// Resolve the effective distro for this component before creating the source manager.
distro, err := sourceproviders.ResolveDistro(env, component)
if err != nil {
return fmt.Errorf("failed to resolve distro for component %#q:\n%w", component.GetName(), err)
}
// Create source manager to handle all source fetching, both local and upstream.
sourceManager, err = sourceproviders.NewSourceManager(env, distro)
if err != nil {
return fmt.Errorf("failed to create source manager:\n%w", err)
}
// Pre-flight check: detect non-empty output directory before any work.
if err := CheckOutputDir(env, options); err != nil {
return err
}
if options.SkipOverlays && !options.WithoutGitRepo {
slog.Warn("dist-git flow has no effect when '--skip-overlays' is set; " +
"synthetic history requires overlays to be applied")
}
if !options.WithoutGitRepo && !options.SkipOverlays {
if err := fileutils.MkdirAll(env.FS(), env.WorkDir()); err != nil {
return fmt.Errorf("failed to create work directory %#q:\n%w", env.WorkDir(), err)
}
}
preparerOpts := buildPreparerOptions(env, distro, options)
preparer, err := sources.NewPreparer(sourceManager, env.FS(), env, env, preparerOpts...)
if err != nil {
return fmt.Errorf("failed to create source preparer:\n%w", err)
}
err = preparer.PrepareSources(env, component, options.OutputDir, !options.SkipOverlays)
if err != nil {
return fmt.Errorf("failed to prepare sources for component %q:\n%w", component.GetName(), err)
}
return nil
}
// buildPreparerOptions assembles [sources.PreparerOption] values based on the resolved
// distro and command-line options.
func buildPreparerOptions(
env *azldev.Env,
distro sourceproviders.ResolvedDistro,
options *PrepareSourcesOptions,
) []sources.PreparerOption {
var opts []sources.PreparerOption
if !options.WithoutGitRepo && !options.SkipOverlays {
opts = append(opts,
sources.WithGitRepo(env, env.LockReader(), distro.Version.ReleaseVer),
sources.WithRPMDevBumpspec(env, env.WorkDir(), ""),
sources.WithDirtyDetection(),
)
}
opts = append(opts,
sources.WithUpstreamProvenance(sources.FedoraDistTag(distro.Ref.Name, distro.Version.ReleaseVer)))
if options.AllowNoHashes {
opts = append(opts, sources.WithAllowNoHashes())
}
if options.SkipSources {
opts = append(opts, sources.WithSkipLookaside())
}
return opts
}
// CheckOutputDir verifies the output directory state before source preparation.
// If the directory exists and is non-empty, it either removes it (when Force is set)
// or returns an actionable error suggesting --force.
func CheckOutputDir(env *azldev.Env, options *PrepareSourcesOptions) error {
dirExists, err := fileutils.DirExists(env.FS(), options.OutputDir)
if err != nil {
return fmt.Errorf("failed to check output directory %#q:\n%w", options.OutputDir, err)
}
if !dirExists {
return nil
}
empty, err := fileutils.IsDirEmpty(env.FS(), options.OutputDir)
if err != nil {
return fmt.Errorf("failed to check if output directory %#q is empty:\n%w", options.OutputDir, err)
}
if empty {
return nil
}
if options.Force {
if err := env.FS().RemoveAll(options.OutputDir); err != nil {
return fmt.Errorf("failed to clean output directory %#q:\n%w", options.OutputDir, err)
}
return nil
}
return fmt.Errorf(
"output directory %#q already exists and is not empty;\n"+
"use --force to delete and recreate it",
options.OutputDir)
}