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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
|
# Quickstart: Go Module VCS Build System for Yocto/BitBake
This guide covers how to create and maintain Go recipes using the `go-mod-vcs` system, which provides reproducible, offline Go builds by fetching dependencies directly from their git repositories.
## Overview
The `go-mod-vcs` system replaces vendor directories with a build-time module cache constructed from git repositories. This provides:
- **Reproducible builds** - Every module comes from a verified git commit
- **Offline builds** - After `do_fetch`, no network access is required
- **Auditable dependencies** - Every dependency is traceable to a specific git commit
- **Smaller recipes** - No need to maintain large vendor directories in source trees
### How It Works
```
┌─────────────────────────────────────────────────────────────────────┐
│ Your Recipe │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ myapp_git.bb │ │ go-mod-git.inc │ │ go-mod-cache.inc │ │
│ │ (your code) │ │ (git fetches) │ │ (module metadata) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │
└───────────────────────────────┬─────────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────────┐ ┌───────────────┐
│do_fetch │ │do_create_ │ │ do_compile │
│ │──────────▶│module_cache │────────▶│ │
│(git) │ │(build cache)│ │(go build) │
└─────────┘ └─────────────┘ └───────────────┘
```
1. **do_fetch** - BitBake fetches all git repositories listed in `go-mod-git.inc`
2. **do_create_module_cache** - The `go-mod-vcs` class builds a Go module cache from git checkouts
3. **do_compile** - Go builds offline using the pre-populated module cache
---
## Converting an Existing Recipe
### Step 1: Add Discovery Configuration
Add these lines to your recipe (before `inherit go`):
```bitbake
# go-mod-discovery configuration
GO_MOD_DISCOVERY_BUILD_TARGET = "./cmd/..." # Your build target
GO_MOD_DISCOVERY_GIT_REPO = "https://github.com/org/repo.git"
GO_MOD_DISCOVERY_GIT_REF = "${SRCREV}"
inherit go-mod-discovery
```
### Step 2: Include the Generated Files
Add includes after your `SRC_URI`:
```bitbake
SRC_URI = "git://github.com/org/repo;branch=main;protocol=https;destsuffix=${GO_SRCURI_DESTSUFFIX}"
include go-mod-git.inc
include go-mod-cache.inc
```
### Step 3: Create Placeholder Files
Create empty placeholder files (they'll be generated):
```bash
cd recipes-containers/myapp/
touch go-mod-git.inc go-mod-cache.inc
```
### Step 4: Run Discovery
```bash
bitbake myapp -c discover_and_generate
```
This will:
1. Build your project with network access to discover dependencies
2. Extract module metadata from the Go module cache
3. Generate `go-mod-git.inc` and `go-mod-cache.inc` files
### Step 5: Update do_compile()
Ensure your `do_compile()` doesn't set conflicting environment variables. The `go-mod-vcs.bbclass` automatically sets:
- `GOMODCACHE` - Points to the built module cache
- `GOPROXY=off` - Enforces offline build
- `GOSUMDB=off` - Disables checksum database
- `GOTOOLCHAIN=local` - Uses the native Go toolchain
A minimal `do_compile()`:
```bitbake
do_compile() {
cd ${S}/src/import
export GOPATH="${S}/src/import/.gopath:${STAGING_DIR_TARGET}/${prefix}/local/go"
export CGO_ENABLED="1"
${GO} build -trimpath ./cmd/...
}
```
### Step 6: Build and Test
```bash
bitbake myapp
```
---
## Creating a New Recipe
### Minimal Recipe Template
```bitbake
SUMMARY = "My Go Application"
HOMEPAGE = "https://github.com/org/myapp"
SRCREV = "abc123def456..."
SRC_URI = "git://github.com/org/myapp;branch=main;protocol=https;destsuffix=${GO_SRCURI_DESTSUFFIX}"
include go-mod-git.inc
include go-mod-cache.inc
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://src/import/LICENSE;md5=..."
GO_IMPORT = "import"
PV = "v1.0.0+git"
# go-mod-discovery configuration
GO_MOD_DISCOVERY_BUILD_TARGET = "./cmd/..."
GO_MOD_DISCOVERY_GIT_REPO = "https://github.com/org/myapp.git"
GO_MOD_DISCOVERY_GIT_REF = "${SRCREV}"
inherit go goarch
inherit go-mod-discovery
do_compile() {
cd ${S}/src/import
export GOPATH="${S}/src/import/.gopath:${STAGING_DIR_TARGET}/${prefix}/local/go"
export CGO_ENABLED="1"
${GO} build -trimpath -o ${B}/myapp ./cmd/myapp
}
do_install() {
install -d ${D}${bindir}
install -m 755 ${B}/myapp ${D}${bindir}/
}
```
### Generate Dependencies
```bash
# Create placeholder files
touch go-mod-git.inc go-mod-cache.inc
# Run discovery
bitbake myapp -c discover_and_generate
# Build
bitbake myapp
```
---
## Updating a Recipe to a New Version
### Quick Update (Same Repository)
1. **Update the SRCREV** in your recipe:
```bitbake
SRCREV = "new_commit_hash_here"
```
2. **Re-run discovery**:
```bash
bitbake myapp -c discover_and_generate
```
3. **Build**:
```bash
bitbake myapp
```
### Full Workflow Example
```bash
# 1. Find the new commit/tag
git ls-remote https://github.com/org/myapp refs/tags/v2.0.0
# 2. Update SRCREV in recipe
# SRCREV = "abc123..."
# 3. Clean old discovery cache (optional, recommended for major updates)
bitbake myapp -c clean_discovery
# 4. Run discovery and generation
bitbake myapp -c discover_and_generate
# 5. Review generated files
git diff recipes-containers/myapp/go-mod-*.inc
# 6. Build and test
bitbake myapp
# 7. Commit changes
git add recipes-containers/myapp/
git commit -m "myapp: update to v2.0.0"
```
---
## Discovery Tasks Reference
| Task | Purpose | Network? |
|------|---------|----------|
| `discover_modules` | Build project and download modules from proxy | Yes |
| `extract_modules` | Extract metadata from cache to JSON | No |
| `generate_modules` | Generate .inc files from metadata | No |
| `discover_and_generate` | All three steps in sequence | Yes |
| `show_upgrade_commands` | Print command lines without running | No |
| `clean_discovery` | Remove the discovery cache | No |
### Step-by-Step vs All-in-One
**All-in-one** (recommended for most cases):
```bash
bitbake myapp -c discover_and_generate
```
**Step-by-step** (useful for debugging):
```bash
bitbake myapp -c discover_modules # Download modules
bitbake myapp -c extract_modules # Extract to JSON
bitbake myapp -c generate_modules # Generate .inc files
```
---
## Configuration Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `GO_MOD_DISCOVERY_BUILD_TARGET` | *(required)* | Go build target (e.g., `./cmd/...`) |
| `GO_MOD_DISCOVERY_GIT_REPO` | *(required for generate)* | Git repository URL |
| `GO_MOD_DISCOVERY_GIT_REF` | `${SRCREV}` | Git commit/tag |
| `GO_MOD_DISCOVERY_SRCDIR` | `${S}/src/import` | Directory containing go.mod |
| `GO_MOD_DISCOVERY_BUILD_TAGS` | `${TAGS}` | Go build tags |
| `GO_MOD_DISCOVERY_RECIPEDIR` | `${FILE_DIRNAME}` | Output directory for .inc files |
---
## Troubleshooting
### "module lookup disabled by GOPROXY=off"
This error during `do_compile` means a module is missing from the cache.
**Fix:**
```bash
# Re-run discovery to find missing modules
bitbake myapp -c discover_and_generate
bitbake myapp
```
### Discovery Cache Location
The discovery cache persists in `${TOPDIR}/go-mod-discovery/${PN}/${PV}/` and survives `bitbake -c cleanall`. To fully reset:
```bash
bitbake myapp -c clean_discovery
bitbake myapp -c discover_and_generate
```
### Viewing Generated Commands
To see what commands would be run without executing them:
```bash
bitbake myapp -c show_upgrade_commands
```
### Build Fails After SRCREV Update
If changing SRCREV causes sstate errors:
```bash
# Clean sstate for the recipe
bitbake myapp -c cleansstate
# Re-run discovery
bitbake myapp -c discover_and_generate
# Build
bitbake myapp
```
### Multiple Source Repositories
For recipes with multiple git sources, use named SRCREVs:
```bitbake
SRCREV_myapp = "abc123..."
SRCREV_plugins = "def456..."
SRCREV_FORMAT = "myapp_plugins"
SRC_URI = "\
git://github.com/org/myapp;name=myapp;branch=main;protocol=https;destsuffix=${GO_SRCURI_DESTSUFFIX} \
git://github.com/org/plugins;name=plugins;branch=main;protocol=https;destsuffix=${GO_SRCURI_DESTSUFFIX}/plugins \
"
GO_MOD_DISCOVERY_GIT_REF = "${SRCREV_myapp}"
```
---
## Example Recipes
### Simple Recipe (rootlesskit)
```bitbake
SRCREV_rootless = "8059d35092db167ec53cae95fb6aa37fc577060c"
SRCREV_FORMAT = "rootless"
SRC_URI = "git://github.com/rootless-containers/rootlesskit;name=rootless;branch=master;protocol=https;destsuffix=${GO_SRCURI_DESTSUFFIX}"
include go-mod-git.inc
include go-mod-cache.inc
GO_MOD_DISCOVERY_BUILD_TARGET = "./cmd/..."
GO_MOD_DISCOVERY_GIT_REPO = "https://github.com/rootless-containers/rootlesskit.git"
GO_MOD_DISCOVERY_GIT_REF = "${SRCREV_rootless}"
inherit go goarch go-mod-discovery
```
### Complex Recipe (k3s with build tags)
```bitbake
TAGS = "static_build netcgo osusergo providerless"
GO_MOD_DISCOVERY_BUILD_TARGET = "./cmd/server/main.go"
GO_MOD_DISCOVERY_GIT_REPO = "https://github.com/rancher/k3s.git"
GO_MOD_DISCOVERY_GIT_REF = "${SRCREV_k3s}"
inherit go goarch go-mod-discovery
```
---
## Files Generated
### go-mod-git.inc
Contains `SRC_URI` entries for each module's git repository:
```bitbake
SRC_URI += "git://github.com/spf13/cobra;protocol=https;nobranch=1;rev=e94f6d0...;name=git_41456771_1;destsuffix=vcs_cache/2d91d6bc..."
```
### go-mod-cache.inc
Contains module metadata and inherits the build class:
```bitbake
inherit go-mod-vcs
GO_MODULE_CACHE_DATA = '[{"module":"github.com/spf13/cobra","version":"v1.8.1","vcs_hash":"2d91d6bc...","timestamp":"2024-06-01T10:31:11Z","subdir":""},...]'
```
---
## Hybrid Mode: Mixing gomod:// and git:// Fetchers
For large projects like k3s with hundreds of modules, the VCS-only approach (all `git://`) can be slow due to the large number of git clones. **Hybrid mode** provides a faster alternative by using:
- `gomod://` - Fast proxy.golang.org downloads for most modules
- `git://` - VCS provenance for selected important modules (e.g., containerd, k8s.io)
### Benefits
| Mode | Fetch Speed | VCS Provenance | Use Case |
|------|-------------|----------------|----------|
| **VCS** (`git://` only) | Slower | Full | Security-critical, audit requirements |
| **Hybrid** (`gomod://` + `git://`) | Faster | Selective | Development, CI, most builds |
### Step 1: Build in VCS Mode First
Ensure your recipe works in VCS mode before converting:
```bash
bitbake myapp -c discover_and_generate
bitbake myapp
```
### Step 2: Run Recommendations
After a successful VCS build, analyze which modules to keep as git:// vs convert to gomod://:
```bash
bitbake myapp -c go_mod_recommend
```
This outputs size-based recommendations and suggests prefixes to keep as `git://`.
### Step 3: Generate Hybrid Files
Use the hybrid conversion script with suggested prefixes:
```bash
python3 ./meta-virtualization/scripts/oe-go-mod-fetcher-hybrid.py \
--recipedir ./meta-virtualization/recipes-containers/myapp/ \
--workdir ${WORKDIR} \
--git "github.com/containerd,k8s.io,sigs.k8s.io"
```
This generates three new files:
- `go-mod-hybrid-gomod.inc` - SRC_URI entries for gomod:// fetcher (with inline checksums)
- `go-mod-hybrid-git.inc` - SRC_URI entries for git:// fetcher (VCS provenance)
- `go-mod-hybrid-cache.inc` - Module metadata for the git:// modules
### Step 4: Configure Recipe for Mode Switching
Update your recipe to allow switching between modes:
```bitbake
# GO_MOD_FETCH_MODE: "vcs" (all git://) or "hybrid" (gomod:// + git://)
GO_MOD_FETCH_MODE ?= "vcs"
# VCS mode: all modules via git://
include ${@ "go-mod-git.inc" if d.getVar("GO_MOD_FETCH_MODE") == "vcs" else ""}
include ${@ "go-mod-cache.inc" if d.getVar("GO_MOD_FETCH_MODE") == "vcs" else ""}
# Hybrid mode: gomod:// for most, git:// for selected
include ${@ "go-mod-hybrid-gomod.inc" if d.getVar("GO_MOD_FETCH_MODE") == "hybrid" else ""}
include ${@ "go-mod-hybrid-git.inc" if d.getVar("GO_MOD_FETCH_MODE") == "hybrid" else ""}
include ${@ "go-mod-hybrid-cache.inc" if d.getVar("GO_MOD_FETCH_MODE") == "hybrid" else ""}
```
### Step 5: Switch Modes
Switch between modes in `local.conf`:
```bash
# Use hybrid mode (faster)
GO_MOD_FETCH_MODE = "hybrid"
# Or use VCS mode (full provenance)
GO_MOD_FETCH_MODE = "vcs"
```
Then rebuild:
```bash
bitbake myapp
```
### Hybrid Script Options
| Option | Description |
|--------|-------------|
| `--recipedir` | Recipe directory containing go-mod-git.inc and go-mod-cache.inc |
| `--workdir` | BitBake workdir for size calculations (optional) |
| `--git "prefixes"` | Comma-separated prefixes to keep as git:// (everything else becomes gomod://) |
| `--gomod "prefixes"` | Comma-separated prefixes to convert to gomod:// (everything else stays git://) |
| `--no-checksums` | Skip fetching SHA256 checksums (not recommended) |
| `--list` | List all modules with sizes |
| `--recommend` | Show size-based conversion recommendations |
### Example: k3s Hybrid Conversion
```bash
# 1. Ensure VCS mode works first
bitbake k3s
# 2. Get recommendations
bitbake k3s -c go_mod_recommend
# 3. Convert with recommended prefixes (keep containerd, k8s.io as git://)
python3 ./meta-virtualization/scripts/oe-go-mod-fetcher-hybrid.py \
--recipedir ./meta-virtualization/recipes-containers/k3s/ \
--git "github.com/containerd,k8s.io,sigs.k8s.io,github.com/rancher"
# 4. Enable hybrid mode
echo 'GO_MOD_FETCH_MODE = "hybrid"' >> conf/local.conf
# 5. Build in hybrid mode
bitbake k3s
```
### Troubleshooting Hybrid Builds
#### "Permission denied" during do_unpack
Go's module cache creates read-only files. The `go-mod-vcs.bbclass` includes automatic permission fixes, but if you hit this on an existing build:
```bash
chmod -R u+w ${WORKDIR}/sources/
bitbake myapp
```
#### BitBake parse errors with special characters
Module paths like `git.sr.ht/~sbinet/gg` contain characters (`~`) that can cause BitBake parse errors in variable flag names. The hybrid script uses inline checksums (`sha256sum=...` in the SRC_URI) to avoid this issue.
---
## Advanced: Manual Script Invocation
For cases where BitBake isn't available or you need more control:
```bash
# Direct generation from git (no BitBake needed)
python3 ./meta-virtualization/scripts/oe-go-mod-fetcher.py \
--git-repo https://github.com/org/myapp.git \
--git-ref abc123... \
--recipedir ./meta-virtualization/recipes-containers/myapp/
# Using existing discovery cache
python3 ./meta-virtualization/scripts/oe-go-mod-fetcher.py \
--discovered-modules ${TOPDIR}/go-mod-discovery/myapp/v1.0.0/modules.json \
--git-repo https://github.com/org/myapp.git \
--git-ref abc123... \
--recipedir ./meta-virtualization/recipes-containers/myapp/
```
---
## Getting Help
- **Show available commands**: `bitbake myapp -c show_upgrade_commands`
- **Architecture details**: See `scripts/ARCHITECTURE.md`
- **Report issues**: Check the generated files and error messages from discovery
|