diff --git a/cmd/state/internal/cmdtree/commit.go b/cmd/state/internal/cmdtree/commit.go index 01ccce6b39..a971ec4dcb 100644 --- a/cmd/state/internal/cmdtree/commit.go +++ b/cmd/state/internal/cmdtree/commit.go @@ -22,7 +22,7 @@ func newCommitCommand(prime *primer.Values) *captain.Command { }, ) - cmd.SetGroup(EnvironmentSetupGroup) + cmd.SetGroup(EnvironmentSetupGroup).SetUnstable(true) return cmd } diff --git a/internal/constants/constants.go b/internal/constants/constants.go index bdcf611455..3d24c34c23 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -69,6 +69,9 @@ const DisableProjectMigrationPrompt = "ACTIVESTATE_CLI_DISABLE_PROJECT_MIGRATION // UpdateBranchEnvVarName is the env var that is used to override which branch to pull the update from const UpdateBranchEnvVarName = "ACTIVESTATE_CLI_UPDATE_BRANCH" +// InstallBuildDependencies is the env var that is used to override whether to install build dependencies +const InstallBuildDependencies = "ACTIVESTATE_CLI_INSTALL_BUILD_DEPENDENCIES" + // InternalConfigFileNameLegacy is effectively the same as InternalConfigName, but includes our preferred extension const InternalConfigFileNameLegacy = "config.yaml" diff --git a/internal/locale/locales/en-us.yaml b/internal/locale/locales/en-us.yaml index 9b854dc311..80cceaee0e 100644 --- a/internal/locale/locales/en-us.yaml +++ b/internal/locale/locales/en-us.yaml @@ -2077,3 +2077,5 @@ projectmigration_confirm: Would you like to perform the migration now? err_searchingredient_toomany: other: Too many ingredients match the query '[ACTIONABLE]{{.V0}}[/RESET]', please try to be more specific. +alternative_unknown_pkg_name: + other: unknown \ No newline at end of file diff --git a/pkg/platform/api/buildplanner/model/buildplan.go b/pkg/platform/api/buildplanner/model/buildplan.go index f5c8bea925..80982d95ec 100644 --- a/pkg/platform/api/buildplanner/model/buildplan.go +++ b/pkg/platform/api/buildplanner/model/buildplan.go @@ -36,7 +36,7 @@ const ( // Tag types TagSource = "src" - TagDependency = "dep" + TagDependency = "deps" TagBuilder = "builder" TagOrphan = "orphans" diff --git a/pkg/platform/model/buildplanner.go b/pkg/platform/model/buildplanner.go index c1ef78b35f..a5112a9256 100644 --- a/pkg/platform/model/buildplanner.go +++ b/pkg/platform/model/buildplanner.go @@ -113,30 +113,6 @@ func (bp *BuildPlanner) FetchBuildResult(commitID strfmt.UUID, owner, project st // response with emtpy targets that we should remove removeEmptyTargets(build) - // Extract the available platforms from the build plan - var bpPlatforms []strfmt.UUID - for _, t := range build.Terminals { - if t.Tag == bpModel.TagOrphan { - continue - } - bpPlatforms = append(bpPlatforms, strfmt.UUID(strings.TrimPrefix(t.Tag, "platform:"))) - } - - // Get the platform ID for the current platform - platformID, err := FilterCurrentPlatform(HostPlatform, bpPlatforms) - if err != nil { - return nil, locale.WrapError(err, "err_filter_current_platform") - } - - // Filter the build terminals to only include the current platform - var filteredTerminals []*bpModel.NamedTarget - for _, t := range build.Terminals { - if platformID.String() == strings.TrimPrefix(t.Tag, "platform:") { - filteredTerminals = append(filteredTerminals, t) - } - } - build.Terminals = filteredTerminals - buildEngine := Alternative for _, s := range build.Sources { if s.Namespace == "builder" && s.Name == "camel" { diff --git a/pkg/platform/runtime/buildplan/buildplan.go b/pkg/platform/runtime/buildplan/buildplan.go index f967923b48..6fe0be1084 100644 --- a/pkg/platform/runtime/buildplan/buildplan.go +++ b/pkg/platform/runtime/buildplan/buildplan.go @@ -1,19 +1,106 @@ package buildplan import ( + "strings" + "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/pkg/platform/api/buildplanner/model" + platformModel "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/platform/runtime/artifact" "github.com/go-openapi/strfmt" ) -// NewMapFromBuildPlan creates an artifact map from a build plan. It creates a +type ArtifactListing struct { + build *model.Build + runtimeClosure artifact.Map + buildtimeClosure artifact.Map + artifactIDs []artifact.ArtifactID +} + +func NewArtifactListing(build *model.Build, buildtimeClosure bool) (*ArtifactListing, error) { + al := &ArtifactListing{build: build} + if buildtimeClosure { + buildtimeClosure, err := newMapFromBuildPlan(al.build, true) + if err != nil { + return nil, errs.Wrap(err, "Could not create buildtime closure") + } + al.buildtimeClosure = buildtimeClosure + } else { + runtimeClosure, err := newMapFromBuildPlan(al.build, false) + if err != nil { + return nil, errs.Wrap(err, "Could not create runtime closure") + } + al.runtimeClosure = runtimeClosure + } + + return al, nil +} + +func (al *ArtifactListing) RuntimeClosure() (artifact.Map, error) { + if al.runtimeClosure != nil { + return al.runtimeClosure, nil + } + + runtimeClosure, err := newMapFromBuildPlan(al.build, false) + if err != nil { + return nil, errs.Wrap(err, "Could not create runtime closure") + } + al.runtimeClosure = runtimeClosure + + return runtimeClosure, nil +} + +func (al *ArtifactListing) BuildtimeClosure() (artifact.Map, error) { + if al.buildtimeClosure != nil { + return al.buildtimeClosure, nil + } + + buildtimeClosure, err := newMapFromBuildPlan(al.build, true) + if err != nil { + return nil, errs.Wrap(err, "Could not create buildtime closure") + } + al.buildtimeClosure = buildtimeClosure + + return buildtimeClosure, nil +} + +func (al *ArtifactListing) ArtifactIDs(buildtimeClosure bool) ([]artifact.ArtifactID, error) { + if al.artifactIDs != nil { + return al.artifactIDs, nil + } + + var artifactMap artifact.Map + var err error + if buildtimeClosure { + artifactMap, err = al.BuildtimeClosure() + if err != nil { + return nil, errs.Wrap(err, "Could not calculate buildtime closure") + } + } else { + artifactMap, err = al.RuntimeClosure() + if err != nil { + return nil, errs.Wrap(err, "Could not calculate runtime closure") + } + } + + for _, artifact := range artifactMap { + al.artifactIDs = append(al.artifactIDs, artifact.ArtifactID) + } + + return al.artifactIDs, nil +} + +// newMapFromBuildPlan creates an artifact map from a build plan. It creates a // lookup table and calls the recursive function buildMap to build up the // artifact map by traversing the build plan from the terminal targets through // all of the runtime dependencies for each of the artifacts in the DAG. -func NewMapFromBuildPlan(build *model.Build) (artifact.Map, error) { + +// Setting calculateBuildtimeClosure as true calculates the artifact map with the buildtime +// dependencies. This is different from the runtime dependency calculation as it +// includes ALL of the input artifacts of the step that generated each artifact. +func newMapFromBuildPlan(build *model.Build, calculateBuildtimeClosure bool) (artifact.Map, error) { res := make(artifact.Map) lookup := make(map[strfmt.UUID]interface{}) @@ -28,8 +115,13 @@ func NewMapFromBuildPlan(build *model.Build) (artifact.Map, error) { lookup[source.NodeID] = source } + filtered, err := filterPlatformTerminals(build) + if err != nil { + return nil, errs.Wrap(err, "Could not filter terminals") + } + var terminalTargetIDs []strfmt.UUID - for _, terminal := range build.Terminals { + for _, terminal := range filtered { // If there is an artifact for this terminal and its mime type is not a state tool artifact // then we need to recurse back through the DAG until we find nodeIDs that are state tool // artifacts. These are the terminal targets. @@ -38,9 +130,13 @@ func NewMapFromBuildPlan(build *model.Build) (artifact.Map, error) { } } + buildMap := buildRuntimeClosureMap + if calculateBuildtimeClosure { + buildMap = buildBuildtimeClosureMap + } + for _, id := range terminalTargetIDs { - err := buildMap(id, lookup, res) - if err != nil { + if err := buildMap(id, lookup, res); err != nil { return nil, errs.Wrap(err, "Could not build map for terminal %s", id) } } @@ -48,6 +144,36 @@ func NewMapFromBuildPlan(build *model.Build) (artifact.Map, error) { return res, nil } +// filterPlatformTerminals filters the build terminal nodes to only include +// terminals that are for the current host platform. +func filterPlatformTerminals(build *model.Build) ([]*model.NamedTarget, error) { + // Extract the available platforms from the build plan + // We are only interested in terminals with the platform tag + var bpPlatforms []strfmt.UUID + for _, t := range build.Terminals { + if !strings.Contains(t.Tag, "platform:") { + continue + } + bpPlatforms = append(bpPlatforms, strfmt.UUID(strings.TrimPrefix(t.Tag, "platform:"))) + } + + // Get the platform ID for the current host platform + platformID, err := platformModel.FilterCurrentPlatform(platformModel.HostPlatform, bpPlatforms) + if err != nil { + return nil, locale.WrapError(err, "err_filter_current_platform") + } + + // Filter the build terminals to only include the current platform + var filteredTerminals []*model.NamedTarget + for _, t := range build.Terminals { + if platformID.String() == strings.TrimPrefix(t.Tag, "platform:") { + filteredTerminals = append(filteredTerminals, t) + } + } + + return filteredTerminals, nil +} + // buildTerminals recursively builds up a list of terminal targets. It expects an ID that // resolves to an artifact. If the artifact's mime type is that of a state tool artifact it // adds it to the terminal listing. Otherwise it looks up the step that generated the artifact @@ -82,7 +208,7 @@ func buildTerminals(nodeID strfmt.UUID, lookup map[strfmt.UUID]interface{}, resu } } -// buildMap recursively builds the artifact map from the lookup table. It expects an ID that +// buildRuntimeClosureMap recursively builds the artifact map from the lookup table. It expects an ID that // represents an artifact. With that ID it retrieves the artifact from the lookup table and // recursively calls itself with each of the artifacts dependencies. Finally, once all of the // dependencies have been processed, it adds the artifact to the result map. @@ -91,7 +217,7 @@ func buildTerminals(nodeID strfmt.UUID, lookup map[strfmt.UUID]interface{}, resu // iterate through the artifact's dependencies, we also have to build up the dependencies of // each of those dependencies. Once we have a complete list of dependencies for the artifact, // we can continue to build up the results map. -func buildMap(baseID strfmt.UUID, lookup map[strfmt.UUID]interface{}, result artifact.Map) error { +func buildRuntimeClosureMap(baseID strfmt.UUID, lookup map[strfmt.UUID]interface{}, result artifact.Map) error { target := lookup[baseID] currentArtifact, ok := target.(*model.Artifact) if !ok { @@ -110,8 +236,7 @@ func buildMap(baseID strfmt.UUID, lookup map[strfmt.UUID]interface{}, result art deps[id] = struct{}{} } - err = buildMap(depID, lookup, result) - if err != nil { + if err := buildRuntimeClosureMap(depID, lookup, result); err != nil { return errs.Wrap(err, "Could not build map for runtime dependency %s", currentArtifact.NodeID) } } @@ -254,8 +379,8 @@ func RecursiveDependenciesFor(a artifact.ArtifactID, artifacts artifact.Map) []a // NewMapFromBuildPlan creates an artifact map from a build plan // where the key is the artifact name rather than the artifact ID. -func NewNamedMapFromBuildPlan(build *model.Build) (artifact.NamedMap, error) { - am, err := NewMapFromBuildPlan(build) +func NewNamedMapFromBuildPlan(build *model.Build, buildtimeClosure bool) (artifact.NamedMap, error) { + am, err := newMapFromBuildPlan(build, buildtimeClosure) if err != nil { return nil, errs.Wrap(err, "Could not create artifact map") } @@ -268,150 +393,132 @@ func NewNamedMapFromBuildPlan(build *model.Build) (artifact.NamedMap, error) { return res, nil } -// BuildtimeArtifacts iterates through all artifacts in a given build and -// adds the artifact's dependencies to a map. This is different from the -// runtime dependency calculation as it includes ALL of the input artifacts of the -// step that generated each artifact. The includeBuilders argument determines whether -// or not to include builder artifacts in the final result. -func BuildtimeArtifacts(build *model.Build, includeBuilders bool) (artifact.Map, error) { - result := make(artifact.Map) - lookup := make(map[strfmt.UUID]interface{}) - - for _, artifact := range build.Artifacts { - lookup[artifact.NodeID] = artifact +// buildBuildtimeClosureMap recursively builds the artifact map from the lookup table. +// If the current artifact is not already contained in the results map it first +// builds the artifacts build-time dependencies and then adds the artifact to the +// results map. +func buildBuildtimeClosureMap(baseID strfmt.UUID, lookup map[strfmt.UUID]interface{}, result artifact.Map) error { + if _, ok := result[baseID]; ok { + // We have already processed this artifact, skipping + return nil } - for _, step := range build.Steps { - lookup[step.StepID] = step + + target := lookup[baseID] + currentArtifact, ok := target.(*model.Artifact) + if !ok { + return errs.New("Incorrect target type for id %s, expected Artifact", baseID) } - for _, source := range build.Sources { - lookup[source.NodeID] = source + + deps := make(map[strfmt.UUID]struct{}) + buildTimeDeps, err := buildBuildClosureDependencies(baseID, lookup, deps, result) + if err != nil { + return errs.Wrap(err, "Could not build buildtime dependencies for artifact %s", baseID) } - for _, a := range build.Artifacts { - if !includeBuilders && a.MimeType == model.XActiveStateBuilderMimeType { + var uniqueDeps []strfmt.UUID + for id := range buildTimeDeps { + if _, ok := deps[id]; !ok { continue } + uniqueDeps = append(uniqueDeps, id) + } - _, ok := result[strfmt.UUID(a.NodeID)] - // We are only interested in artifacts that have not already been added - // to the result and that have been submitted to be built. - if !ok && a.Status != model.ArtifactNotSubmitted { - // deps here refer to the dependencies of the artifact itself. - // This includes the direct dependencies, which we get through - // the RuntimeDependencies field, as well as the inputs of the - // step that generated the artifact. - deps := make(map[strfmt.UUID]struct{}) - for _, depID := range a.RuntimeDependencies { - artifact, ok := lookup[depID].(*model.Artifact) - if ok { - if !includeBuilders && artifact.MimeType == model.XActiveStateBuilderMimeType { - continue - } - } - - // Add our current dependency to the map of dependencies - // and then recursively add all of its dependencies. - deps[depID] = struct{}{} - recursiveDeps, err := generateBuildtimeDependencies(depID, includeBuilders, lookup, deps) - if err != nil { - return nil, errs.Wrap(err, "Could not resolve runtime dependencies for artifact: %s", depID) - } - - // This is a list of all of the dependencies of the current - // artifact, including the dependencies of its dependencies. - for id := range recursiveDeps { - deps[id] = struct{}{} - } - } - - // We need to convert the map of dependencies to a list of - // dependencies. - var uniqueDeps []strfmt.UUID - for depID := range deps { - uniqueDeps = append(uniqueDeps, depID) - } - - // We need to get the source information for the artifact. - // This is done by looking at the step that generated the - // artifact and then looking at the source that was used - // in that step. - info, err := getSourceInfo(a.GeneratedBy, lookup) - if err != nil { - return nil, errs.Wrap(err, "Could not resolve source information") - } + info, err := getSourceInfo(currentArtifact.GeneratedBy, lookup) + if err != nil { + return errs.Wrap(err, "Could not resolve source information") + } - result[strfmt.UUID(a.NodeID)] = artifact.Artifact{ - ArtifactID: strfmt.UUID(a.NodeID), - Name: info.Name, - Namespace: info.Namespace, - Version: &info.Version, - RequestedByOrder: false, - GeneratedBy: a.GeneratedBy, - Dependencies: uniqueDeps, - } - } + result[strfmt.UUID(currentArtifact.NodeID)] = artifact.Artifact{ + ArtifactID: strfmt.UUID(currentArtifact.NodeID), + Name: info.Name, + Namespace: info.Namespace, + Version: &info.Version, + RequestedByOrder: true, + GeneratedBy: currentArtifact.GeneratedBy, + Dependencies: uniqueDeps, + URL: currentArtifact.URL, } - return result, nil + return nil } -// generateBuildtimeDependencies recursively iterates through an artifacts dependencies -// looking to the step that generated the artifact and then to ALL of the artifacts that -// are inputs to that step. This will lead to the result containing more than what is in -// the runtime closure. The includeBuilders argument is used to determine if we should -// include artifacts that are builders. -func generateBuildtimeDependencies(artifactID strfmt.UUID, includeBuilders bool, lookup map[strfmt.UUID]interface{}, result map[strfmt.UUID]struct{}) (map[strfmt.UUID]struct{}, error) { - artifact, ok := lookup[artifactID].(*model.Artifact) - if !ok { - _, sourceOK := lookup[artifactID].(*model.Source) - if sourceOK { - // Dependency is a source, skipping - return nil, nil - } - return nil, errs.New("Incorrect target type for id %s, expected Artifact or Source", artifactID) +// buildBuildClosureDependencies is a recursive function that builds up a map +// of build-time dependencies for a given artifact if it is not already present +// in the results map. It first iterates through the runtime dependencies of the +// artifact recursively adding all of the dependencies to the results map. +// Then it iterates through the inputs of the step that generated the +// artifact and recursively adds all of those dependencies as well. +func buildBuildClosureDependencies(artifactID strfmt.UUID, lookup map[strfmt.UUID]interface{}, deps map[strfmt.UUID]struct{}, result artifact.Map) (map[strfmt.UUID]struct{}, error) { + if _, ok := result[artifactID]; ok { + // We have already processed this artifact, skipping + return nil, nil } - if !includeBuilders && artifact.MimeType == model.XActiveStateBuilderMimeType { - return nil, nil + currentArtifact, ok := lookup[artifactID].(*model.Artifact) + if !ok { + return nil, errs.New("Incorrect target type for id %s, expected Artifact", artifactID) } // We iterate through the direct dependencies of the artifact // and recursively add all of the dependencies of those artifacts map. - for _, depID := range artifact.RuntimeDependencies { - result[depID] = struct{}{} - _, err := generateBuildtimeDependencies(depID, includeBuilders, lookup, result) + // This is the same as the runtime closure calculation. + for _, depID := range currentArtifact.RuntimeDependencies { + deps[depID] = struct{}{} + artifactDeps := make(map[strfmt.UUID]struct{}) + _, err := buildBuildClosureDependencies(depID, lookup, artifactDeps, result) if err != nil { - return nil, errs.Wrap(err, "Could not build map for runtime dependencies of artifact %s", artifact.NodeID) + return nil, errs.Wrap(err, "Could not build map for runtime dependencies of artifact %s", currentArtifact.NodeID) } } - step, ok := lookup[artifact.GeneratedBy].(*model.Step) + // Here we iterate through the inputs of the step that generated the + // artifact, specifically the inputs that are tagged as dependencies. + // We recursively add all of the dependencies of the step intputs to + // the result map. This is the buildtime closure calculation. + step, ok := lookup[currentArtifact.GeneratedBy].(*model.Step) if !ok { - _, ok := lookup[artifact.GeneratedBy].(*model.Source) - if !ok { - return nil, errs.New("Incorrect target type for id %s, expected Step or Source", artifact.GeneratedBy) - } - - // Artifact was not generated by a step, skipping + // Artifact was not generated by a step, skipping because these + // artifacts do not need to be built. return nil, nil } // We iterate through the inputs of the step that generated the - // artifact and recursively add all of the dependencies of those - // artifacts, skipping over the builder artifacts as those resolve - // directly to sources. This is because they are not built and therefore - // not generated by a step. + // artifact, specifically the inputs that are tagged as dependencies and + // build a build-time closure for each. for _, input := range step.Inputs { - if input.Tag == model.TagBuilder { + if input.Tag != model.TagDependency { continue } - for _, id := range input.NodeIDs { - _, err := generateBuildtimeDependencies(id, includeBuilders, lookup, result) + + for _, inputID := range input.NodeIDs { + deps[inputID] = struct{}{} + _, err := buildBuildClosureDependencies(inputID, lookup, deps, result) if err != nil { - return nil, errs.Wrap(err, "Could not build map for step dependencies of artifact %s", artifact.NodeID) + return nil, errs.Wrap(err, "Could not build map for step dependencies of artifact %s", currentArtifact.NodeID) } } } - return result, nil + var uniqueDeps []strfmt.UUID + for id := range deps { + uniqueDeps = append(uniqueDeps, id) + } + + info, err := getSourceInfo(currentArtifact.GeneratedBy, lookup) + if err != nil { + return nil, errs.Wrap(err, "Could not resolve source information") + } + + result[strfmt.UUID(currentArtifact.NodeID)] = artifact.Artifact{ + ArtifactID: strfmt.UUID(currentArtifact.NodeID), + Name: info.Name, + Namespace: info.Namespace, + Version: &info.Version, + RequestedByOrder: true, + GeneratedBy: currentArtifact.GeneratedBy, + Dependencies: uniqueDeps, + URL: currentArtifact.URL, + } + + return deps, nil } diff --git a/pkg/platform/runtime/buildplan/changeset.go b/pkg/platform/runtime/buildplan/changeset.go index 9a5614e856..48e8619554 100644 --- a/pkg/platform/runtime/buildplan/changeset.go +++ b/pkg/platform/runtime/buildplan/changeset.go @@ -6,13 +6,13 @@ import ( "github.com/ActiveState/cli/pkg/platform/runtime/artifact" ) -func NewArtifactChangesetByBuildPlan(oldBuildPlan *model.Build, build *model.Build, requestedOnly bool) (artifact.ArtifactChangeset, error) { - old, err := NewNamedMapFromBuildPlan(oldBuildPlan) +func NewArtifactChangesetByBuildPlan(oldBuildPlan *model.Build, build *model.Build, requestedOnly, buildtimeClosure bool) (artifact.ArtifactChangeset, error) { + old, err := NewNamedMapFromBuildPlan(oldBuildPlan, buildtimeClosure) if err != nil { return artifact.ArtifactChangeset{}, errs.Wrap(err, "failed to build map from old build plan") } - new, err := NewNamedMapFromBuildPlan(build) + new, err := NewNamedMapFromBuildPlan(build, buildtimeClosure) if err != nil { return artifact.ArtifactChangeset{}, errs.Wrap(err, "failed to build map from new build plan") } @@ -22,8 +22,8 @@ func NewArtifactChangesetByBuildPlan(oldBuildPlan *model.Build, build *model.Bui return cs, nil } -func NewBaseArtifactChangesetByBuildPlan(build *model.Build, requestedOnly bool) (artifact.ArtifactChangeset, error) { - new, err := NewNamedMapFromBuildPlan(build) +func NewBaseArtifactChangesetByBuildPlan(build *model.Build, requestedOnly, buildtimeClosure bool) (artifact.ArtifactChangeset, error) { + new, err := NewNamedMapFromBuildPlan(build, buildtimeClosure) if err != nil { return artifact.ArtifactChangeset{}, errs.Wrap(err, "failed to build map from new build plan") } diff --git a/pkg/platform/runtime/setup/implementations/alternative/resolver.go b/pkg/platform/runtime/setup/implementations/alternative/resolver.go new file mode 100644 index 0000000000..28abf13492 --- /dev/null +++ b/pkg/platform/runtime/setup/implementations/alternative/resolver.go @@ -0,0 +1,21 @@ +package alternative + +import ( + "github.com/ActiveState/cli/internal/locale" + "github.com/ActiveState/cli/pkg/platform/runtime/artifact" +) + +type Resolver struct { + artifactsForNameResolving artifact.Map +} + +func NewResolver(artifactsForNameResolving artifact.Map) *Resolver { + return &Resolver{artifactsForNameResolving: artifactsForNameResolving} +} + +func (r *Resolver) ResolveArtifactName(id artifact.ArtifactID) string { + if artf, ok := r.artifactsForNameResolving[id]; ok { + return artf.Name + } + return locale.T("alternative_unknown_pkg_name") +} diff --git a/pkg/platform/runtime/setup/implementations/alternative/runtime.go b/pkg/platform/runtime/setup/implementations/alternative/runtime.go index 452c0fe80a..f6a0930c9e 100644 --- a/pkg/platform/runtime/setup/implementations/alternative/runtime.go +++ b/pkg/platform/runtime/setup/implementations/alternative/runtime.go @@ -18,12 +18,11 @@ import ( ) type Setup struct { - artifactsForNameResolving artifact.Map - store *store.Store + store *store.Store } -func NewSetup(store *store.Store, artifactsForNameResolving artifact.Map) *Setup { - return &Setup{store: store, artifactsForNameResolving: artifactsForNameResolving} +func NewSetup(store *store.Store) *Setup { + return &Setup{store: store} } func (s *Setup) DeleteOutdatedArtifacts(changeset artifact.ArtifactChangeset, storedArtifacted, alreadyInstalled store.StoredArtifactMap) error { @@ -140,10 +139,7 @@ func artifactsContainFile(file string, artifactCache map[artifact.ArtifactID]sto } func (s *Setup) ResolveArtifactName(a artifact.ArtifactID) string { - if artf, ok := s.artifactsForNameResolving[a]; ok { - return artf.Name - } - return locale.Tl("alternative_unknown_pkg_name", "unknown") + return locale.T("alternative_unknown_pkg_name") } func (s *Setup) DownloadsFromBuild(build model.Build, artifacts map[strfmt.UUID]artifact.Artifact) (download []artifact.ArtifactDownload, err error) { diff --git a/pkg/platform/runtime/setup/implementations/camel/resolver.go b/pkg/platform/runtime/setup/implementations/camel/resolver.go new file mode 100644 index 0000000000..1075f1db3a --- /dev/null +++ b/pkg/platform/runtime/setup/implementations/camel/resolver.go @@ -0,0 +1,16 @@ +package camel + +import ( + "github.com/ActiveState/cli/internal/locale" + "github.com/ActiveState/cli/pkg/platform/runtime/artifact" +) + +type Resolver struct{} + +func NewResolver() *Resolver { + return &Resolver{} +} + +func (r *Resolver) ResolveArtifactName(_ artifact.ArtifactID) string { + return locale.Tl("camel_bundle_name", "legacy bundle") +} diff --git a/pkg/platform/runtime/setup/setup.go b/pkg/platform/runtime/setup/setup.go index 371c6c41ef..252aa45cbd 100644 --- a/pkg/platform/runtime/setup/setup.go +++ b/pkg/platform/runtime/setup/setup.go @@ -136,7 +136,6 @@ type Setup struct { type Setuper interface { // DeleteOutdatedArtifacts deletes outdated artifact as best as it can DeleteOutdatedArtifacts(artifact.ArtifactChangeset, store.StoredArtifactMap, store.StoredArtifactMap) error - ResolveArtifactName(artifact.ArtifactID) string DownloadsFromBuild(build bpModel.Build, artifacts map[strfmt.UUID]artifact.Artifact) (download []artifact.ArtifactDownload, err error) } @@ -147,6 +146,10 @@ type ArtifactSetuper interface { Unarchiver() unarchiver.Unarchiver } +type ArtifactResolver interface { + ResolveArtifactName(artifact.ArtifactID) string +} + type artifactInstaller func(artifact.ArtifactID, string, ArtifactSetuper) error type artifactUninstaller func() error @@ -398,26 +401,45 @@ func (s *Setup) fetchAndInstallArtifactsFromBuildPlan(installFunc artifactInstal return nil, nil, errs.Wrap(err, "Could not handle SolveSuccess event") } + // If the build is not ready or if we are installing the buildtime closure + // then we need to include the buildtime closure in the changed artifacts + // and the progress reporting. + includeBuildtimeClosure := strings.EqualFold(os.Getenv(constants.InstallBuildDependencies), "true") || !buildResult.BuildReady + // Compute and handle the change summary - // runtimeAndBuildtimeArtifacts records all artifacts that will need to be built in order to obtain the runtime. - // Disabled due to DX-2033. - // Please use this var when we come back to this in the future as we need to make a clear distinction between this - // and runtime-only artifacts. - // var runtimeAndBuildtimeArtifacts artifact.Map - var runtimeArtifacts artifact.Map // Artifacts required for the runtime to function - if buildResult.Build != nil { - runtimeArtifacts, err = buildplan.NewMapFromBuildPlan(buildResult.Build) + var requestedArtifacts artifact.Map // Artifacts required for the runtime to function + artifactListing, err := buildplan.NewArtifactListing(buildResult.Build, includeBuildtimeClosure) + if err != nil { + return nil, nil, errs.Wrap(err, "Failed to create artifact listing") + } + + // If we are installing build dependencies, then the requested artifacts + // will include the buildtime closure. Otherwise, we only need the runtime + // closure. + if strings.EqualFold(os.Getenv(constants.InstallBuildDependencies), "true") { + logging.Debug("Installing build dependencies") + requestedArtifacts, err = artifactListing.BuildtimeClosure() + if err != nil { + return nil, nil, errs.Wrap(err, "Failed to compute buildtime closure") + } + } else { + requestedArtifacts, err = artifactListing.RuntimeClosure() if err != nil { return nil, nil, errs.Wrap(err, "Failed to create artifact map from build plan") } } - setup, err := s.selectSetupImplementation(buildResult.BuildEngine, runtimeArtifacts) + resolver, err := selectArtifactResolver(buildResult, artifactListing) + if err != nil { + return nil, nil, errs.Wrap(err, "Failed to select artifact resolver") + } + + setup, err := s.selectSetupImplementation(buildResult.BuildEngine) if err != nil { return nil, nil, errs.Wrap(err, "Failed to select setup implementation") } - downloadablePrebuiltResults, err := setup.DownloadsFromBuild(*buildResult.Build, runtimeArtifacts) + downloadablePrebuiltResults, err := setup.DownloadsFromBuild(*buildResult.Build, requestedArtifacts) if err != nil { if errors.Is(err, artifact.CamelRuntimeBuilding) { return nil, nil, locale.WrapInputError(err, "build_status_in_progress", "", apimodel.ProjectURL(s.target.Owner(), s.target.Name(), s.target.CommitUUID().String())) @@ -427,7 +449,7 @@ func (s *Setup) fetchAndInstallArtifactsFromBuildPlan(installFunc artifactInstal // buildResult doesn't have namespace info and will happily report internal only artifacts downloadablePrebuiltResults = funk.Filter(downloadablePrebuiltResults, func(ad artifact.ArtifactDownload) bool { - ar, ok := runtimeArtifacts[ad.ArtifactID] + ar, ok := requestedArtifacts[ad.ArtifactID] if !ok { return true } @@ -444,7 +466,7 @@ func (s *Setup) fetchAndInstallArtifactsFromBuildPlan(installFunc artifactInstal s.analytics.Event(anaConsts.CatRuntimeDebug, anaConsts.ActRuntimeBuild, dimensions) } - changedArtifacts, err := buildplan.NewBaseArtifactChangesetByBuildPlan(buildResult.Build, false) + changedArtifacts, err := buildplan.NewBaseArtifactChangesetByBuildPlan(buildResult.Build, false, includeBuildtimeClosure) if err != nil { return nil, nil, errs.Wrap(err, "Could not compute base artifact changeset") } @@ -455,7 +477,7 @@ func (s *Setup) fetchAndInstallArtifactsFromBuildPlan(installFunc artifactInstal } if oldBuildPlan != nil { - changedArtifacts, err = buildplan.NewArtifactChangesetByBuildPlan(oldBuildPlan, buildResult.Build, false) + changedArtifacts, err = buildplan.NewArtifactChangesetByBuildPlan(oldBuildPlan, buildResult.Build, false, includeBuildtimeClosure) if err != nil { return nil, nil, errs.Wrap(err, "Could not compute artifact changeset") } @@ -469,12 +491,12 @@ func (s *Setup) fetchAndInstallArtifactsFromBuildPlan(installFunc artifactInstal alreadyInstalled := reusableArtifacts(buildResult.Build.Artifacts, storedArtifacts) // Report resolved artifacts - artifactIDs := []artifact.ArtifactID{} - for _, a := range runtimeArtifacts { - artifactIDs = append(artifactIDs, a.ArtifactID) + artifactIDs, err := artifactListing.ArtifactIDs(includeBuildtimeClosure) + if err != nil { + return nil, nil, errs.Wrap(err, "Could not get artifact IDs from build plan") } - artifactNames := artifact.ResolveArtifactNames(setup.ResolveArtifactName, artifactIDs) + artifactNames := artifact.ResolveArtifactNames(resolver.ResolveArtifactName, artifactIDs) artifactNamesList := []string{} for _, n := range artifactNames { artifactNamesList = append(artifactNamesList, n) @@ -493,23 +515,19 @@ func (s *Setup) fetchAndInstallArtifactsFromBuildPlan(installFunc artifactInstal ) artifactsToInstall := []artifact.ArtifactID{} - // buildtimeArtifacts := runtimeArtifacts + var artifactsToBuild artifact.Map if buildResult.BuildReady { - // If the build is already done we can just look at the downloadable artifacts as they will be a fully accurate - // prediction of what we will be installing. for _, a := range downloadablePrebuiltResults { if _, alreadyInstalled := alreadyInstalled[a.ArtifactID]; !alreadyInstalled { artifactsToInstall = append(artifactsToInstall, a.ArtifactID) } } + artifactsToBuild, err = artifactListing.BuildtimeClosure() } else { - // If the build is not yet complete then we have to speculate as to the artifacts that will be installed. - // The actual number of installable artifacts may be lower than what we have here, we can only do a best effort. - for _, a := range runtimeArtifacts { - if _, alreadyInstalled := alreadyInstalled[a.ArtifactID]; !alreadyInstalled { - artifactsToInstall = append(artifactsToInstall, a.ArtifactID) - } - } + artifactsToBuild, err = artifactListing.RuntimeClosure() + } + if err != nil { + return nil, nil, errs.Wrap(err, "Failed to compute artifacts to build") } // The log file we want to use for builds @@ -526,7 +544,7 @@ func (s *Setup) fetchAndInstallArtifactsFromBuildPlan(installFunc artifactInstal ArtifactNames: artifactNames, LogFilePath: logFilePath, ArtifactsToBuild: func() []artifact.ArtifactID { - return artifact.ArtifactIDsFromBuildPlanMap(runtimeArtifacts) // This does not account for cached builds + return artifact.ArtifactIDsFromBuildPlanMap(artifactsToBuild) // This does not account for cached builds }(), // Yes these have the same value; this is intentional. // Separating these out just allows us to be more explicit and intentional in our event handling logic. @@ -546,7 +564,7 @@ func (s *Setup) fetchAndInstallArtifactsFromBuildPlan(installFunc artifactInstal s.analytics.Event(anaConsts.CatRuntimeDebug, anaConsts.ActRuntimeDownload, dimensions) } - err = s.installArtifactsFromBuild(buildResult, runtimeArtifacts, artifact.ArtifactIDsToMap(artifactsToInstall), downloadablePrebuiltResults, setup, installFunc, logFilePath) + err = s.installArtifactsFromBuild(buildResult, requestedArtifacts, artifact.ArtifactIDsToMap(artifactsToInstall), downloadablePrebuiltResults, setup, resolver, installFunc, logFilePath) if err != nil { return nil, nil, err } @@ -591,7 +609,7 @@ func aggregateErrors() (chan<- error, <-chan error) { return bgErrs, aggErr } -func (s *Setup) installArtifactsFromBuild(buildResult *model.BuildResult, artifacts artifact.Map, artifactsToInstall map[artifact.ArtifactID]struct{}, downloads []artifact.ArtifactDownload, setup Setuper, installFunc artifactInstaller, logFilePath string) error { +func (s *Setup) installArtifactsFromBuild(buildResult *model.BuildResult, artifacts artifact.Map, artifactsToInstall map[artifact.ArtifactID]struct{}, downloads []artifact.ArtifactDownload, setup Setuper, resolver ArtifactResolver, installFunc artifactInstaller, logFilePath string) error { // Artifacts are installed in two stages // - The first stage runs concurrently in MaxConcurrency worker threads (download, unpacking, relocation) // - The second stage moves all files into its final destination is running in a single thread (using the mainthread library) to avoid file conflicts @@ -601,16 +619,16 @@ func (s *Setup) installArtifactsFromBuild(buildResult *model.BuildResult, artifa if err := s.handleEvent(events.BuildSkipped{}); err != nil { return errs.Wrap(err, "Could not handle BuildSkipped event") } - err = s.installFromBuildResult(buildResult, artifacts, artifactsToInstall, downloads, setup, installFunc) + err = s.installFromBuildResult(buildResult, artifacts, artifactsToInstall, downloads, setup, resolver, installFunc) } else { - err = s.installFromBuildLog(buildResult, artifacts, artifactsToInstall, setup, installFunc, logFilePath) + err = s.installFromBuildLog(buildResult, artifacts, artifactsToInstall, setup, resolver, installFunc, logFilePath) } return err } // setupArtifactSubmitFunction returns a function that sets up an artifact and can be submitted to a workerpool -func (s *Setup) setupArtifactSubmitFunction(a artifact.ArtifactDownload, ar *artifact.Artifact, expectedArtifactInstalls map[artifact.ArtifactID]struct{}, buildResult *model.BuildResult, setup Setuper, installFunc artifactInstaller, errors chan<- error) func() { +func (s *Setup) setupArtifactSubmitFunction(a artifact.ArtifactDownload, ar *artifact.Artifact, expectedArtifactInstalls map[artifact.ArtifactID]struct{}, buildResult *model.BuildResult, setup Setuper, resolver ArtifactResolver, installFunc artifactInstaller, errors chan<- error) func() { return func() { // If artifact has no valid download, just count it as completed and return if strings.Contains(ar.URL, "as-builds/noop") || @@ -637,21 +655,21 @@ func (s *Setup) setupArtifactSubmitFunction(a artifact.ArtifactDownload, ar *art unarchiver := as.Unarchiver() archivePath, err := s.obtainArtifact(a, unarchiver.Ext()) if err != nil { - name := setup.ResolveArtifactName(a.ArtifactID) + name := resolver.ResolveArtifactName(a.ArtifactID) errors <- locale.WrapError(err, "artifact_download_failed", "", name, a.ArtifactID.String()) return } err = installFunc(a.ArtifactID, archivePath, as) if err != nil { - name := setup.ResolveArtifactName(a.ArtifactID) + name := resolver.ResolveArtifactName(a.ArtifactID) errors <- locale.WrapError(err, "artifact_setup_failed", "", name, a.ArtifactID.String()) return } } } -func (s *Setup) installFromBuildResult(buildResult *model.BuildResult, artifacts artifact.Map, artifactsToInstall map[artifact.ArtifactID]struct{}, downloads []artifact.ArtifactDownload, setup Setuper, installFunc artifactInstaller) error { +func (s *Setup) installFromBuildResult(buildResult *model.BuildResult, artifacts artifact.Map, artifactsToInstall map[artifact.ArtifactID]struct{}, downloads []artifact.ArtifactDownload, setup Setuper, resolver ArtifactResolver, installFunc artifactInstaller) error { logging.Debug("Installing artifacts from build result") errs, aggregatedErr := aggregateErrors() mainthread.Run(func() { @@ -665,7 +683,7 @@ func (s *Setup) installFromBuildResult(buildResult *model.BuildResult, artifacts if arv, ok := artifacts[a.ArtifactID]; ok { ar = &arv } - wp.Submit(s.setupArtifactSubmitFunction(a, ar, map[artifact.ArtifactID]struct{}{}, buildResult, setup, installFunc, errs)) + wp.Submit(s.setupArtifactSubmitFunction(a, ar, map[artifact.ArtifactID]struct{}{}, buildResult, setup, resolver, installFunc, errs)) } wp.StopWait() @@ -674,7 +692,7 @@ func (s *Setup) installFromBuildResult(buildResult *model.BuildResult, artifacts return <-aggregatedErr } -func (s *Setup) installFromBuildLog(buildResult *model.BuildResult, artifacts artifact.Map, artifactsToInstall map[artifact.ArtifactID]struct{}, setup Setuper, installFunc artifactInstaller, logFilePath string) error { +func (s *Setup) installFromBuildLog(buildResult *model.BuildResult, artifacts artifact.Map, artifactsToInstall map[artifact.ArtifactID]struct{}, setup Setuper, resolver ArtifactResolver, installFunc artifactInstaller, logFilePath string) error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -715,7 +733,7 @@ func (s *Setup) installFromBuildLog(buildResult *model.BuildResult, artifacts ar logging.Debug("Unmonitored artifact buildlog event discarded: %s", a.ArtifactID) continue } - wp.Submit(s.setupArtifactSubmitFunction(a, ar, artifactsToInstall, buildResult, setup, installFunc, errs)) + wp.Submit(s.setupArtifactSubmitFunction(a, ar, artifactsToInstall, buildResult, setup, resolver, installFunc, errs)) } }() @@ -864,10 +882,10 @@ func (s *Setup) unpackArtifact(ua unarchiver.Unarchiver, tarballPath string, tar return numUnpackedFiles, ua.Unarchive(proxy, i, targetDir) } -func (s *Setup) selectSetupImplementation(buildEngine model.BuildEngine, artifacts artifact.Map) (Setuper, error) { +func (s *Setup) selectSetupImplementation(buildEngine model.BuildEngine) (Setuper, error) { switch buildEngine { case model.Alternative: - return alternative.NewSetup(s.store, artifacts), nil + return alternative.NewSetup(s.store), nil case model.Camel: return camel.NewSetup(s.store), nil default: @@ -875,6 +893,28 @@ func (s *Setup) selectSetupImplementation(buildEngine model.BuildEngine, artifac } } +func selectArtifactResolver(buildResult *model.BuildResult, artifactListing *buildplan.ArtifactListing) (ArtifactResolver, error) { + var artifacts artifact.Map + var err error + if buildResult.BuildReady || strings.EqualFold(os.Getenv(constants.InstallBuildDependencies), "true") { + artifacts, err = artifactListing.BuildtimeClosure() + } else { + artifacts, err = artifactListing.RuntimeClosure() + } + if err != nil { + return nil, errs.Wrap(err, "Failed to create artifact map from build plan") + } + + switch buildResult.BuildEngine { + case model.Alternative: + return alternative.NewResolver(artifacts), nil + case model.Camel: + return camel.NewResolver(), nil + default: + return nil, errs.New("Unknown build engine: %s", buildResult.BuildEngine) + } +} + func (s *Setup) selectArtifactSetupImplementation(buildEngine model.BuildEngine, a artifact.ArtifactID) (ArtifactSetuper, error) { switch buildEngine { case model.Alternative: diff --git a/test/integration/checkout_int_test.go b/test/integration/checkout_int_test.go index 8a075b7798..e016dc1d0f 100644 --- a/test/integration/checkout_int_test.go +++ b/test/integration/checkout_int_test.go @@ -262,6 +262,28 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckoutCaseInsensitive() { suite.Assert().NotContains(string(bytes), "ACTIVESTATE-CLI/SMALL-PYTHON", "kept incorrect namespace case") } +func (suite *CheckoutIntegrationTestSuite) TestCheckoutBuildtimeClosure() { + suite.OnlyRunForTags(tagsuite.Checkout) + + if runtime.GOOS != "linux" { + suite.T().Skip("Skipping buildtime closure test on non-linux platform") + } + + ts := e2e.New(suite.T(), false) + defer ts.Close() + + cp := ts.SpawnWithOpts( + e2e.OptArgs("checkout", "ActiveState-CLI/small-python#5a1e49e5-8ceb-4a09-b605-ed334474855b"), + e2e.OptAppendEnv(constants.InstallBuildDependencies+"=true"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), + ) + // Expect the number of build deps to be 27 which is more than the number of runtime deps. + // Also expect libxcrypt which should not be in the runtime closure. + cp.Expect("27") + cp.Expect("libxcrypt") + cp.ExpectExitCode(0) +} + func TestCheckoutIntegrationTestSuite(t *testing.T) { suite.Run(t, new(CheckoutIntegrationTestSuite)) }