Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions internal/app/azldev/cmds/component/preparesources.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ Only one component may be selected at a time.`,
RunE: azldev.RunFuncWithExtraArgs(func(env *azldev.Env, args []string) (interface{}, error) {
options.ComponentFilter.ComponentNamePatterns = append(args, options.ComponentFilter.ComponentNamePatterns...)

return nil, PrepareComponentSources(env, &options)
report, err := PrepareComponentSources(env, &options)
if err != nil {
return nil, err
}

return report.Sources, nil
Comment on lines +56 to +61
}),
ValidArgsFunction: components.GenerateComponentNameCompletions,
Annotations: map[string]string{
Expand All @@ -77,24 +82,24 @@ Only one component may be selected at a time.`,
return cmd
}

func PrepareComponentSources(env *azldev.Env, options *PrepareSourcesOptions) error {
func PrepareComponentSources(env *azldev.Env, options *PrepareSourcesOptions) (*sources.ProvenanceReport, 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)
return nil, fmt.Errorf("failed to resolve components:\n%w", err)
}

if comps.Len() == 0 {
return errors.New("no components were selected; " +
return nil, 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())
return nil, fmt.Errorf("expected exactly one component, got %d", comps.Len())
}

component := comps.Components()[0]
Expand All @@ -107,18 +112,18 @@ func PrepareComponentSources(env *azldev.Env, options *PrepareSourcesOptions) er
// 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)
return nil, 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)
return nil, 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
return nil, err
}

if options.SkipOverlays && !options.WithoutGitRepo {
Expand All @@ -140,15 +145,15 @@ func PrepareComponentSources(env *azldev.Env, options *PrepareSourcesOptions) er

preparer, err := sources.NewPreparer(sourceManager, env.FS(), env, env, preparerOpts...)
if err != nil {
return fmt.Errorf("failed to create source preparer:\n%w", err)
return nil, fmt.Errorf("failed to create source preparer:\n%w", err)
}

err = preparer.PrepareSources(env, component, options.OutputDir, !options.SkipOverlays)
report, 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, fmt.Errorf("failed to prepare sources for component %q:\n%w", component.GetName(), err)
}

return nil
return report, nil
}

// CheckOutputDir verifies the output directory state before source preparation.
Expand Down
2 changes: 1 addition & 1 deletion internal/app/azldev/cmds/component/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ func prepareComponentSources(
return nil, fmt.Errorf("creating source preparer for %#q:\n%w", componentName, err)
}

if prepErr := preparer.PrepareSources(env, comp, componentDir, true /*applyOverlays*/); prepErr != nil {
if _, prepErr := preparer.PrepareSources(env, comp, componentDir, true /*applyOverlays*/); prepErr != nil {
return nil, fmt.Errorf("preparing sources for %#q:\n%w", componentName, prepErr)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func DownloadSources(env *azldev.Env, options *DownloadSourcesOptions) error {
for _, uri := range lookasideBaseURIs {
slog.Info("Trying lookaside base URI", "uri", uri)

uriErr := lookasideDownloader.ExtractSourcesFromRepo(
_, uriErr := lookasideDownloader.ExtractSourcesFromRepo(
env, options.Directory, packageName, uri, nil, extractOpts...,
)
if uriErr == nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func TestDownloadSources_StandaloneMode(t *testing.T) {
mockDownloader := fedorasource_test.NewMockFedoraSourceDownloader(ctrl)
mockDownloader.EXPECT().
ExtractSourcesFromRepo(gomock.Any(), testPkgDir, "curl", testLookasideURI, gomock.Any()).
Return(nil)
Return(nil, nil)

options := &downloadsources.DownloadSourcesOptions{
Directory: testPkgDir,
Expand All @@ -94,7 +94,7 @@ func TestDownloadSources_StandaloneMode_NoSourcesFile(t *testing.T) {
mockDownloader := fedorasource_test.NewMockFedoraSourceDownloader(ctrl)
mockDownloader.EXPECT().
ExtractSourcesFromRepo(gomock.Any(), testPkgDir, "curl", testLookasideURI, gomock.Any()).
Return(nil)
Return(nil, nil)

options := &downloadsources.DownloadSourcesOptions{
Directory: testPkgDir,
Expand Down Expand Up @@ -129,7 +129,7 @@ func TestDownloadSources_ComponentMode(t *testing.T) {
ExtractSourcesFromRepo(
gomock.Any(), testPkgDir, "curl", expectedURI, gomock.Any(),
).
Return(nil)
Return(nil, nil)

options := &downloadsources.DownloadSourcesOptions{
Directory: testPkgDir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (b *Builder) prepSourcesForSRPM(
return "", fmt.Errorf("failed to create work dir for source preparation:\n%w", err)
}

err = b.sourcePreparer.PrepareSources(ctx, component, preparedSourcesDir, true /*applyOverlays?*/)
_, err = b.sourcePreparer.PrepareSources(ctx, component, preparedSourcesDir, true /*applyOverlays?*/)
if err != nil {
return "", fmt.Errorf("failed to prepare sources:\n%w", err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ func setupBuilder(t *testing.T) *componentBuilderTestParams {
func(
_ context.Context, component components.Component,
outputDir string, _ ...sourceproviders.FetchComponentOption,
) error {
) ([]sourceproviders.SourceProvenance, error) {
// Create the expected spec file.
specPath := filepath.Join(outputDir, component.GetName()+".spec")

return fileutils.WriteFile(testEnv.Env.FS(), specPath, []byte("# test spec"), fileperms.PublicFile)
return nil, fileutils.WriteFile(testEnv.Env.FS(), specPath, []byte("# test spec"), fileperms.PublicFile)
},
)

sourceManager.EXPECT().FetchFiles(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
sourceManager.EXPECT().FetchFiles(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil, nil)

preparer, err := sources.NewPreparer(sourceManager, testEnv.Env.FS(), testEnv.Env, testEnv.Env)

Expand Down
16 changes: 16 additions & 0 deletions internal/app/azldev/core/sources/provenance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package sources

import "github.com/microsoft/azure-linux-dev-tools/internal/providers/sourceproviders"

// ProvenanceReport is the output of a source preparation run, listing
// every file that was downloaded and where it came from.
type ProvenanceReport struct {
// ComponentName is the name of the component whose sources were prepared.
ComponentName string `json:"componentName" table:"Component"`

// Sources lists the provenance of each downloaded source file.
Sources []sourceproviders.SourceProvenance `json:"sources" table:"-"`
}
37 changes: 26 additions & 11 deletions internal/app/azldev/core/sources/sourceprep.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@ type SourcePreparer interface {
// within the output directory will be build-time dependencies on external packages (RPMs), including those
// relied on to be present implicitly within the build root, or expressed via BuildRequires or DynamicBuildRequires
// in the component's spec file and any defaults from the macros used to interpret the spec file.
PrepareSources(ctx context.Context, component components.Component, outputDir string, applyOverlays bool) error
//
// Returns a [ProvenanceReport] listing each file that was downloaded and where it came from.
// The report only includes files that were actually downloaded; pre-existing files are omitted.
PrepareSources(
ctx context.Context, component components.Component,
outputDir string, applyOverlays bool,
) (*ProvenanceReport, error)

// DiffSources computes a unified diff showing the changes that overlays apply to a component's sources.
// The component's sources are fetched once into a subdirectory of baseDir, then copied to a second
Expand Down Expand Up @@ -214,16 +220,20 @@ func NewPreparer(
// PrepareSources implements the [SourcePreparer] interface.
func (p *sourcePreparerImpl) PrepareSources(
ctx context.Context, component components.Component, outputDir string, applyOverlays bool,
) error {
) (*ProvenanceReport, error) {
var allProvenance []sourceproviders.SourceProvenance

// Use the source manager to fetch source files (archives, patches, etc.)
// Skip this step when skipLookaside is set — source tarballs are not needed
// for rendering and are the most expensive download.
if !p.skipLookaside {
err := p.sourceManager.FetchFiles(ctx, component, outputDir)
fileProv, err := p.sourceManager.FetchFiles(ctx, component, outputDir)
if err != nil {
return fmt.Errorf("failed to fetch source files for component %#q:\n%w",
return nil, fmt.Errorf("failed to fetch source files for component %#q:\n%w",
component.GetName(), err)
}

allProvenance = append(allProvenance, fileProv...)
}

// Preserve the upstream .git directory only when dist-git creation is
Expand All @@ -239,20 +249,22 @@ func (p *sourcePreparerImpl) PrepareSources(
}

// Use the source manager to fetch the component (spec file and sidecar files).
err := p.sourceManager.FetchComponent(ctx, component, outputDir, fetchOpts...)
compProv, err := p.sourceManager.FetchComponent(ctx, component, outputDir, fetchOpts...)
if err != nil {
return fmt.Errorf("failed to fetch sources for component %#q:\n%w",
return nil, fmt.Errorf("failed to fetch sources for component %#q:\n%w",
component.GetName(), err)
}

allProvenance = append(allProvenance, compProv...)

if applyOverlays {
err := p.applyOverlaysToSources(ctx, component, outputDir)
if err != nil {
return err
return nil, err
}

if err := p.updateSourcesFile(component, outputDir); err != nil {
return fmt.Errorf("failed to update 'sources' file for component %#q:\n%w",
return nil, fmt.Errorf("failed to update 'sources' file for component %#q:\n%w",
component.GetName(), err)
}
} else {
Expand All @@ -264,12 +276,15 @@ func (p *sourcePreparerImpl) PrepareSources(
// Record the changes as synthetic git history when dist-git creation is enabled.
if p.withGitRepo {
if err := p.trySyntheticHistory(ctx, component, outputDir); err != nil {
return fmt.Errorf("failed to generate synthetic history for component %#q:\n%w",
return nil, fmt.Errorf("failed to generate synthetic history for component %#q:\n%w",
component.GetName(), err)
}
}

return nil
return &ProvenanceReport{
ComponentName: component.GetName(),
Sources: allProvenance,
}, nil
}

// applyOverlaysToSources writes the macros file and then applies all overlays.
Expand Down Expand Up @@ -584,7 +599,7 @@ func (p *sourcePreparerImpl) DiffSources(
defer fileutils.RemoveAllAndUpdateErrorIfNil(p.fs, originalDir, &err)

// Prepare sources without applying overlays, to get the original tree.
if err := p.PrepareSources(ctx, component, originalDir, false /* applyOverlays */); err != nil {
if _, err := p.PrepareSources(ctx, component, originalDir, false /* applyOverlays */); err != nil {
return nil, err
}

Expand Down
Loading
Loading