feat(build): introduce build configuration for channel inclusion
- Add `build.yaml.example` for configuring which channels to compile into the binary. - Implement `genbuild` script to generate channel imports based on `build.yaml`. - Update `Makefile` to run `genbuild` automatically during the build process. - Create documentation in `BUILD_CONFIG.md` to guide users on configuring and using the build system. - Move channel imports to `channels_imports.go`, generated by `genbuild`, to reduce binary size.
This commit is contained in:
parent
cadcdc0b41
commit
0cf07dee81
6 changed files with 290 additions and 15 deletions
9
Makefile
9
Makefile
|
|
@ -72,8 +72,13 @@ BINARY_PATH=$(BUILD_DIR)/$(BINARY_NAME)-$(PLATFORM)-$(ARCH)
|
|||
# Default target
|
||||
all: build
|
||||
|
||||
## generate: Run generate
|
||||
generate:
|
||||
## genbuild: Generate channel imports from build.yaml (channels.include). Run before build to shrink binary.
|
||||
genbuild:
|
||||
@echo "Generating channel imports from build.yaml..."
|
||||
@$(GO) run ./scripts/genbuild
|
||||
|
||||
## generate: Run genbuild and go generate
|
||||
generate: genbuild
|
||||
@echo "Run generate..."
|
||||
@rm -r ./$(CMD_DIR)/workspace 2>/dev/null || true
|
||||
@$(GO) generate ./...
|
||||
|
|
|
|||
17
build.yaml.example
Normal file
17
build.yaml.example
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Build configuration: control which channels (and optionally providers/tools in the future)
|
||||
# are compiled into the binary to reduce size. Copy to build.yaml and edit. Used when you run:
|
||||
# make build
|
||||
# (or run: go run ./scripts/genbuild before go build).
|
||||
#
|
||||
# See docs/BUILD_CONFIG.md.
|
||||
|
||||
channels:
|
||||
# Preferred allowlist model: list the channel names you want to include.
|
||||
# If empty or omitted, all discovered channels under pkg/channels/ are included.
|
||||
#
|
||||
# Channel names must match the channel package directory under pkg/channels/.
|
||||
include: []
|
||||
# include: [telegram, whatsapp, discord, slack, feishu, dingtalk, wecom, qq, line, onebot, maixcam, pico, whatsapp_native]
|
||||
|
||||
# Optional denylist model:
|
||||
# skip: [telegram, whatsapp]
|
||||
20
cmd/picoclaw/internal/gateway/channels_imports.go
Normal file
20
cmd/picoclaw/internal/gateway/channels_imports.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Code generated by scripts/genbuild. DO NOT EDIT.
|
||||
// Edit build.yaml and run: go run ./scripts/genbuild (or make build).
|
||||
|
||||
package gateway
|
||||
|
||||
import (
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/dingtalk"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/discord"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/feishu"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/line"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/maixcam"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/onebot"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/pico"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/qq"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/slack"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/telegram"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/wecom"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/whatsapp"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/whatsapp_native"
|
||||
)
|
||||
|
|
@ -13,19 +13,7 @@ import (
|
|||
"github.com/sipeed/picoclaw/pkg/agent"
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/dingtalk"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/discord"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/feishu"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/line"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/maixcam"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/onebot"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/pico"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/qq"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/slack"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/telegram"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/wecom"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/whatsapp"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/whatsapp_native"
|
||||
// Channel imports are in channels_imports.go (generated by scripts/genbuild from build.yaml).
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/cron"
|
||||
"github.com/sipeed/picoclaw/pkg/devices"
|
||||
|
|
|
|||
45
docs/BUILD_CONFIG.md
Normal file
45
docs/BUILD_CONFIG.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
## Build configuration (optional smaller binary)
|
||||
|
||||
You can control **which channels are compiled into the binary** to reduce size. This is done **before** the build by parsing a config file and generating which channel packages get compiled in.
|
||||
|
||||
### How it works
|
||||
|
||||
1. **Config**: Create `build.yaml` in the repo root (copy from `build.yaml.example`).
|
||||
2. **Allowlist**: Set `channels.include` to the channel names you want to compile in (e.g. `[telegram, whatsapp]`).
|
||||
3. **Generate**: Run the generator so it writes `cmd/picoclaw/internal/gateway/channels_imports.go` with only the selected channel imports.
|
||||
4. **Build**: Run `make build`. The generator is run automatically as part of `make build`.
|
||||
|
||||
If there is no `build.yaml`, or `channels.include` is empty / omitted, **all discovered channels** under `pkg/channels/` are included by default.
|
||||
|
||||
### Quick start
|
||||
|
||||
```bash
|
||||
# Copy example and edit (list channels to include)
|
||||
cp build.yaml.example build.yaml
|
||||
|
||||
# Edit build.yaml, e.g.:
|
||||
# channels:
|
||||
# include: [telegram, whatsapp]
|
||||
|
||||
# Build (generate runs first and overwrites channel imports)
|
||||
make build
|
||||
```
|
||||
|
||||
Or without Make:
|
||||
|
||||
```bash
|
||||
go run ./scripts/genbuild # generate channel imports from build.yaml
|
||||
go build -o picoclaw ./cmd/picoclaw
|
||||
```
|
||||
|
||||
### Channel names
|
||||
|
||||
Channel names in `channels.include` must match the directory name under `pkg/channels/`. The generator **automatically discovers** all channel packages by reading the `pkg/channels/` directory, so you usually don't need to touch any code when adding a new channel—just create `pkg/channels/<name>/` and, if desired, list `<name>` in `channels.include`.
|
||||
|
||||
|
||||
|
||||
Optional denylist options also work:
|
||||
|
||||
- `channels.skip: [telegram, whatsapp]`
|
||||
|
||||
If `channels.include` is set, it **wins** over any skip settings.
|
||||
200
scripts/genbuild/main.go
Normal file
200
scripts/genbuild/main.go
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
// genbuild reads build.yaml (channels.include) and
|
||||
// generates cmd/picoclaw/internal/gateway/channels_imports.go so only the
|
||||
// selected channels are compiled in, reducing binary size.
|
||||
//
|
||||
// Run before build: go run ./scripts/genbuild
|
||||
// Or use: make build (which runs this via the generate target).
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
buildConfigName = "build.yaml"
|
||||
outputPath = "cmd/picoclaw/internal/gateway/channels_imports.go"
|
||||
modulePath = "github.com/sipeed/picoclaw"
|
||||
)
|
||||
|
||||
type buildConfig struct {
|
||||
// Preferred allowlist model:
|
||||
// channels:
|
||||
// include: [telegram, whatsapp]
|
||||
// Optional denylist model:
|
||||
// channels:
|
||||
// skip: [telegram, whatsapp]
|
||||
Channels struct {
|
||||
Include []string `yaml:"include"`
|
||||
Skip []string `yaml:"skip"`
|
||||
} `yaml:"channels"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
repoRoot, err := findRepoRoot()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "genbuild: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := loadConfig(repoRoot)
|
||||
|
||||
allChannels, err := discoverChannels(repoRoot)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "genbuild: discover channels: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
include := selectChannels(allChannels, cfg)
|
||||
|
||||
outPath := filepath.Join(repoRoot, outputPath)
|
||||
if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "genbuild: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
content := generateImports(include)
|
||||
if err := os.WriteFile(outPath, []byte(content), 0o644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "genbuild: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "genbuild: wrote %s (%d channels)\n", outputPath, len(include))
|
||||
}
|
||||
|
||||
func findRepoRoot() (string, error) {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for {
|
||||
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
||||
return dir, nil
|
||||
}
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
return "", fmt.Errorf("go.mod not found (run from repo root)")
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
|
||||
func loadConfig(repoRoot string) *buildConfig {
|
||||
cfg := &buildConfig{}
|
||||
path := filepath.Join(repoRoot, buildConfigName)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
// No build.yaml: include all channels
|
||||
return cfg
|
||||
}
|
||||
if err := yaml.Unmarshal(data, cfg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "genbuild: warning: %s: %v (including all channels)\n", path, err)
|
||||
return cfg
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// discoverChannels returns all channel package names (directory names under pkg/channels/).
|
||||
func discoverChannels(repoRoot string) ([]string, error) {
|
||||
dir := filepath.Join(repoRoot, "pkg", "channels")
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var channels []string
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := e.Name()
|
||||
if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") {
|
||||
continue
|
||||
}
|
||||
channels = append(channels, name)
|
||||
}
|
||||
|
||||
sort.Strings(channels)
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
// selectChannels applies the config to the discovered channels, preferring the
|
||||
// allowlist model (channels.include), and optionally the denylist model (channels.skip).
|
||||
func selectChannels(all []string, cfg *buildConfig) []string {
|
||||
if len(all) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
available := make(map[string]struct{}, len(all))
|
||||
for _, ch := range all {
|
||||
available[ch] = struct{}{}
|
||||
}
|
||||
|
||||
// 1) Preferred allowlist: channels.include
|
||||
if len(cfg.Channels.Include) > 0 {
|
||||
var include []string
|
||||
for _, raw := range cfg.Channels.Include {
|
||||
name := strings.TrimSpace(raw)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := available[name]; !ok {
|
||||
fmt.Fprintf(os.Stderr, "genbuild: warning: channel %q listed in channels.include but no such pkg/channels/%s\n", name, name)
|
||||
continue
|
||||
}
|
||||
include = append(include, name)
|
||||
}
|
||||
if len(include) == 0 {
|
||||
// Nothing matched; fall back to "all" to avoid surprising empty builds.
|
||||
return all
|
||||
}
|
||||
sort.Strings(include)
|
||||
return include
|
||||
}
|
||||
|
||||
// 2) denylist: channels.skip.
|
||||
skipSet := make(map[string]struct{})
|
||||
for _, raw := range cfg.Channels.Skip {
|
||||
name := strings.TrimSpace(raw)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
skipSet[name] = struct{}{}
|
||||
}
|
||||
|
||||
if len(skipSet) == 0 {
|
||||
// No config at all or no skip entries: include all channels.
|
||||
return all
|
||||
}
|
||||
|
||||
var include []string
|
||||
for _, ch := range all {
|
||||
if _, skip := skipSet[ch]; skip {
|
||||
continue
|
||||
}
|
||||
include = append(include, ch)
|
||||
}
|
||||
return include
|
||||
}
|
||||
|
||||
func generateImports(channels []string) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("// Code generated by scripts/genbuild. DO NOT EDIT.\n")
|
||||
b.WriteString("// Edit build.yaml and run: go run ./scripts/genbuild (or make build).\n\n")
|
||||
b.WriteString("package gateway\n\n")
|
||||
b.WriteString("import (\n")
|
||||
for _, ch := range channels {
|
||||
b.WriteString("\t_ \"")
|
||||
b.WriteString(modulePath)
|
||||
b.WriteString("/pkg/channels/")
|
||||
b.WriteString(ch)
|
||||
b.WriteString("\"\n")
|
||||
}
|
||||
b.WriteString(")\n")
|
||||
return b.String()
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue