Skip to content

Commit 54cf381

Browse files
committed
feat: ar transport support, lots of cleanups
1 parent d420f6d commit 54cf381

12 files changed

Lines changed: 332 additions & 55 deletions

File tree

addons/bootstrap/apt/common/google-cloud.go

Lines changed: 0 additions & 28 deletions
This file was deleted.

addons/bootstrap/apt/steps-packages.go

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,18 @@ const StepNameUpdate = "apt update"
1717
// WithExtraUpdate adds a secondary `apt update` step with the given name. It
1818
// will always run after the main `apt update` step. You may pass additional
1919
// ordering constraints in the options.
20+
//
21+
// This is equivalent to calling [bootstrap.WithSteps] with the result of [ExtraUpdateStep].
2022
func WithExtraUpdate(name string, opts ...bootstrap.StepOpt) bootstrap.Option {
23+
return bootstrap.WithSteps(ExtraUpdateStep(name, opts...))
24+
}
25+
26+
// ExtraUpdateStep creates a secondary `apt update` step with the given name. It
27+
// will always run after the main `apt update` step. You should pass additional
28+
// ordering constraints in the options.
29+
func ExtraUpdateStep(name string, opts ...bootstrap.StepOpt) *bootstrap.Step {
2130
opts = append([]bootstrap.StepOpt{bootstrap.AfterSteps(StepNameUpdate)}, opts...)
22-
return bootstrap.WithSteps(bootstrap.NewStep(name, DoUpdate, opts...))
31+
return bootstrap.NewStep(name, DoUpdate, opts...)
2332
}
2433

2534
func updateStep() *bootstrap.Step {
@@ -62,9 +71,7 @@ const StepNameInstall = "apt install"
6271
func installStep() *bootstrap.Step {
6372
return bootstrap.NewStep(
6473
StepNameInstall,
65-
func(ctx *bootstrap.Context) error {
66-
return DoInstall(ctx, []string{"--no-install-recommends"}, nil, "")
67-
},
74+
doInstall,
6875
bootstrap.AfterSteps(StepNameUpdate),
6976
bootstrap.SimFunc(simInstall),
7077
)
@@ -76,13 +83,34 @@ func installStep() *bootstrap.Step {
7683
//
7784
// You likely want to pair this with [WithExtraUpdate], one or more steps to
7885
// add new apt sources that call [ChangedSources] and [AddPackages].
86+
//
87+
// This is equivalent to calling [bootstrap.WithSteps] with the result of
88+
// [ExtraInstallStep].
7989
func WithExtraInstall(name string, opts ...bootstrap.StepOpt) bootstrap.Option {
80-
opts = append([]bootstrap.StepOpt{bootstrap.AfterSteps(StepNameInstall)}, opts...)
81-
return bootstrap.WithSteps(bootstrap.NewStep(name, DoUpdate, opts...))
90+
return bootstrap.WithSteps(ExtraInstallStep(name, opts...))
91+
}
92+
93+
// ExtraInstallStep creates a secondary `apt install` step with the given name.
94+
// It will always run after the main `apt install` step. You should pass
95+
// additional ordering constraints in the options, e.g. ensuring this runs after
96+
// a custom update and package selection steps.
97+
func ExtraInstallStep(name string, opts ...bootstrap.StepOpt) *bootstrap.Step {
98+
opts = append(
99+
[]bootstrap.StepOpt{
100+
bootstrap.AfterSteps(StepNameInstall),
101+
bootstrap.SimFunc(simInstall),
102+
},
103+
opts...,
104+
)
105+
return bootstrap.NewStep(name, doInstall, opts...)
82106
}
83107

84108
var pendingPackages = bootstrap.NewKey[map[string]struct{}]("pending-apt-packages")
85109

110+
func doInstall(ctx *bootstrap.Context) error {
111+
return DoInstall(ctx, []string{"--no-install-recommends"}, nil, "")
112+
}
113+
86114
// DoInstall runs `apt install -y ...` with:
87115
//
88116
// - Any extra options you pass. Including `--no-install-recommends` is often
@@ -254,6 +282,21 @@ func AddPackagesStep(
254282
)
255283
}
256284

285+
func AddExtraPackagesStep(
286+
stepName string,
287+
packages ...string,
288+
) *bootstrap.Step {
289+
mark := func(ctx *bootstrap.Context) error {
290+
AddPackages(ctx, packages...)
291+
return nil
292+
}
293+
return bootstrap.NewStep(
294+
stepName,
295+
mark,
296+
bootstrap.SimFunc(mark),
297+
)
298+
}
299+
257300
// WithPackages is an option for [Configure] that will register a step to
258301
// mark the given package(s) to be installed by the main `apt install` step.
259302
func WithPackages(

addons/bootstrap/apt/steps-sources.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ import (
77
// SourceInstallStep creates a bootstrap step that installs the given APT
88
// source.
99
//
10-
// You should always adjust the returned step with [bootstrap.BeforeSteps],
11-
// commonly [bootstrap.StepNameAptUpdate].
10+
// You should always adjust the step with before/after constraints. For example
11+
// if this is a public source, you would typically use [bootstrap.BeforeSteps],
12+
// commonly [bootstrap.StepNameAptUpdate] (or call [PublicSourceInstallSteps]).
13+
// If this is a private source, then you would typically use
14+
// [bootstrap.AfterSteps] and [bootstrap.BeforeSteps] to ensure it runs after
15+
// the normal apt setup and before the secondary apt install that uses packages
16+
// from this private source.
1217
func SourceInstallStep(
1318
installer *SourceInstaller,
1419
opts ...bootstrap.StepOpt,

addons/bootstrap/env-detect.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package bootstrap
2+
3+
import (
4+
"os"
5+
)
6+
7+
var isInContainerKey = NewKey[bool]("is-in-container")
8+
9+
func IsInContainer(ctx *Context) bool {
10+
if v, ok := Get(ctx, isInContainerKey); ok {
11+
return v
12+
}
13+
// TODO: better detection than this
14+
_, err := os.Stat("/.dockerenv")
15+
v := err == nil
16+
Save(ctx, isInContainerKey, v)
17+
return v
18+
}
19+
20+
func SkipInContainer() StepOpt {
21+
return SkipFunc(func(ctx *Context) (bool, error) {
22+
return IsInContainer(ctx), nil
23+
})
24+
}
25+
26+
var hasGUIKey = NewKey[bool]("has-gui")
27+
28+
func HasGUI(ctx *Context) bool {
29+
if v, ok := Get(ctx, hasGUIKey); ok {
30+
return v
31+
}
32+
v := os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != ""
33+
Save(ctx, hasGUIKey, v)
34+
return v
35+
}
36+
37+
func SkipIfNoGUI() StepOpt {
38+
return SkipFunc(func(ctx *Context) (bool, error) {
39+
return !HasGUI(ctx), nil
40+
})
41+
}

addons/bootstrap/plan.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ func (p *Plan) prepare(ctx context.Context) (*Context, error) {
118118
for _, s := range p.pending {
119119
names = append(names, s.name)
120120
}
121+
p.debugCircularDeps()
121122
return nil, fmt.Errorf("plan has unresolved dependencies blocking %s", strings.Join(names, ", "))
122123
}
123124
if len(p.ordered) == 0 {
@@ -127,6 +128,62 @@ func (p *Plan) prepare(ctx context.Context) (*Context, error) {
127128
return bc, nil
128129
}
129130

131+
func (p *Plan) debugCircularDeps() {
132+
isOrdered := make(map[string]bool, len(p.ordered))
133+
for _, s := range p.ordered {
134+
isOrdered[s.name] = true
135+
}
136+
afterByName := make(map[string]map[string]struct{}, len(p.pending))
137+
for _, s := range p.pending {
138+
m := maps.Clone(s.after)
139+
// trim out satisfied deps
140+
for n := range m {
141+
if _, ok := isOrdered[n]; ok {
142+
delete(m, n)
143+
}
144+
}
145+
afterByName[s.name] = m
146+
}
147+
for _, s := range p.pending {
148+
for beforeName := range s.before {
149+
if _, ok := isOrdered[beforeName]; !ok {
150+
if afterByName[beforeName] == nil {
151+
afterByName[beforeName] = make(map[string]struct{})
152+
}
153+
afterByName[beforeName][s.name] = struct{}{}
154+
}
155+
}
156+
}
157+
// find cycles
158+
visited := make(map[string]bool, len(afterByName))
159+
var visit func(name string, stack []string) bool
160+
visit = func(name string, stack []string) bool {
161+
if visited[name] {
162+
return false
163+
}
164+
visited[name] = true
165+
stack = append(stack, name)
166+
for depName := range afterByName[name] {
167+
for i, sn := range stack {
168+
if sn == depName {
169+
fmt.Printf("circular dependency: %s\n", strings.Join(append(stack[i:], depName), " -> "))
170+
return true
171+
}
172+
}
173+
if visit(depName, stack) {
174+
return true
175+
}
176+
}
177+
return false
178+
}
179+
for name := range afterByName {
180+
if !visited[name] {
181+
// don't stop after one cycle, find them all
182+
visit(name, nil)
183+
}
184+
}
185+
}
186+
130187
func (p *Plan) Sim(ctx context.Context) error {
131188
bc, err := p.prepare(ctx)
132189
if err != nil {

addons/bootstrap/step.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package bootstrap
22

3-
import "fastcat.org/go/gdev/internal"
3+
import (
4+
"fmt"
5+
6+
"fastcat.org/go/gdev/internal"
7+
)
48

59
type Step struct {
610
name string
@@ -64,3 +68,35 @@ func AfterSteps(names ...string) StepOpt {
6468
}
6569
}
6670
}
71+
72+
func SkipFunc(f func(*Context) (bool, error)) StepOpt {
73+
return func(s *Step) {
74+
origRun := s.run
75+
s.run = func(ctx *Context) error {
76+
skip, err := f(ctx)
77+
if err != nil {
78+
return err
79+
}
80+
if skip {
81+
fmt.Println("Skipping", s.name)
82+
return nil
83+
}
84+
return origRun(ctx)
85+
}
86+
origSim := s.sim
87+
s.sim = func(ctx *Context) error {
88+
skip, err := f(ctx)
89+
if err != nil {
90+
return err
91+
}
92+
if skip {
93+
fmt.Println("Skipping", s.name)
94+
return nil
95+
}
96+
if origSim != nil {
97+
return origSim(ctx)
98+
}
99+
return nil
100+
}
101+
}
102+
}

addons/docker/bootstrap.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@ var configureBootstrap = sync.OnceFunc(func() {
1717
"Select common Docker packages",
1818
"docker.io",
1919
"docker-buildx",
20-
"golang-docker-credential-helpers",
2120
),
2221
bootstrap.WithSteps(
22+
apt.AddPackagesStep(
23+
"Select docker credential helper(s)",
24+
"golang-docker-credential-helpers",
25+
).With(
26+
bootstrap.BeforeSteps(apt.StepNameInstall),
27+
bootstrap.SkipInContainer(),
28+
),
2329
apt.AddPackageIfAvailable(
2430
"Select docker-cli if needed",
2531
// this is only on Ubuntu 25.04+ and Debian 13+

addons/gcloud/addon.go

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"fastcat.org/go/gdev/addons"
1212
"fastcat.org/go/gdev/addons/bootstrap"
1313
"fastcat.org/go/gdev/addons/bootstrap/apt"
14-
apt_common "fastcat.org/go/gdev/addons/bootstrap/apt/common"
1514
"fastcat.org/go/gdev/lib/shx"
1615
)
1716

@@ -34,8 +33,9 @@ func init() {
3433
}
3534

3635
type config struct {
37-
skipLogin bool
38-
allowedDomains []string
36+
skipLogin bool
37+
includeTransport bool
38+
allowedDomains []string
3939
}
4040

4141
type option func(*config)
@@ -58,29 +58,49 @@ func initialize() error {
5858
// WithSkipLogin causes the bootstrap sequence to no-op the login step.
5959
func WithSkipLogin() option {
6060
return func(c *config) {
61+
if configuredBootstrap {
62+
panic("WithSkipLogin must be called before first Configure()")
63+
}
6164
c.skipLogin = true
6265
}
6366
}
6467

68+
func WithAptTransport() option {
69+
return func(c *config) {
70+
if configuredBootstrap {
71+
panic("WithAptTransport must be called before first Configure()")
72+
}
73+
c.includeTransport = true
74+
}
75+
}
76+
6577
func WithAllowedDomains(domains ...string) option {
6678
return func(c *config) {
6779
c.allowedDomains = domains
6880
}
6981
}
7082

71-
var configureBootstrap = sync.OnceFunc(func() {
72-
// TODO: allow customization of the bootstrap steps somehow?
73-
// We can't do the bootstrap config in initialize(), so it gets a little tricky
74-
bootstrap.Configure(
75-
bootstrap.WithSteps(apt.PublicSourceInstallSteps(apt_common.GoogleCloudInstaller())...),
76-
apt.WithPackages("Select gcloud packages", "google-cloud-cli"),
77-
bootstrap.WithSteps(bootstrap.NewStep(
78-
ConfigureStepName,
79-
configureGcloud,
80-
bootstrap.AfterSteps(apt.StepNameInstall),
81-
)),
82-
)
83-
})
83+
var (
84+
configuredBootstrap bool
85+
configureBootstrap = sync.OnceFunc(func() {
86+
sources := []*apt.SourceInstaller{CLISourceInstaller()}
87+
packages := []string{"google-cloud-cli"}
88+
if addon.Config.includeTransport {
89+
sources = append(sources, AptTransportSourceInstaller())
90+
packages = append(packages, "apt-transport-artifact-registry")
91+
}
92+
bootstrap.Configure(
93+
bootstrap.WithSteps(apt.PublicSourceInstallSteps(sources...)...),
94+
apt.WithPackages("Select gcloud packages", packages...),
95+
bootstrap.WithSteps(bootstrap.NewStep(
96+
ConfigureStepName,
97+
configureGcloud,
98+
bootstrap.AfterSteps(apt.StepNameInstall),
99+
)),
100+
)
101+
configuredBootstrap = true
102+
})
103+
)
84104

85105
const ConfigureStepName = "Configure gcloud"
86106

0 commit comments

Comments
 (0)