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
|
# Default target
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
## generate: Run generate
|
## genbuild: Generate channel imports from build.yaml (channels.include). Run before build to shrink binary.
|
||||||
generate:
|
genbuild:
|
||||||
|
@echo "Generating channel imports from build.yaml..."
|
||||||
|
@$(GO) run ./scripts/genbuild
|
||||||
|
|
||||||
|
## generate: Run genbuild and go generate
|
||||||
|
generate: genbuild
|
||||||
@echo "Run generate..."
|
@echo "Run generate..."
|
||||||
@rm -r ./$(CMD_DIR)/workspace 2>/dev/null || true
|
@rm -r ./$(CMD_DIR)/workspace 2>/dev/null || true
|
||||||
@$(GO) generate ./...
|
@$(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/agent"
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
"github.com/sipeed/picoclaw/pkg/channels"
|
"github.com/sipeed/picoclaw/pkg/channels"
|
||||||
_ "github.com/sipeed/picoclaw/pkg/channels/dingtalk"
|
// Channel imports are in channels_imports.go (generated by scripts/genbuild from build.yaml).
|
||||||
_ "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"
|
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
"github.com/sipeed/picoclaw/pkg/cron"
|
"github.com/sipeed/picoclaw/pkg/cron"
|
||||||
"github.com/sipeed/picoclaw/pkg/devices"
|
"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