Files
livekit/magefile.go
T
David Zhao 258f5add2d protocol update: explicit AddTrack to move negotiation initiation to server side.
In order to avoid race conditions with WebRTC, where either side could initiate an offer when tracks have changes, we'll always initiate them from the SFU side.
2021-01-09 23:40:29 -08:00

339 lines
6.7 KiB
Go

// +build mage
package main
import (
"crypto/sha1"
"fmt"
"go/build"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"github.com/magefile/mage/mg"
log "github.com/pion/ion-log"
)
const (
protoChecksumFile = ".checksumproto"
goChecksumFile = ".checksumgo"
)
// Default target to run when none is specified
// If not set, running mage will list available targets
var Default = Build
var checksummer = NewChecksummer(".", goChecksumFile, ".go")
func init() {
checksummer.IgnoredPaths = []string{
"cmd/server/wire_gen.go",
"pkg/rtc/types/typesfakes",
}
}
// explicitly reinstall all deps
func Deps() error {
return installTools(true)
}
// regenerate protobuf
func Proto() error {
protoChecksummer := NewChecksummer("proto", protoChecksumFile, ".proto")
if !protoChecksummer.IsChanged() {
return nil
}
fmt.Println("generating protobuf")
target := "proto/livekit"
if err := os.MkdirAll(target, 0755); err != nil {
return err
}
protoc, err := getToolPath("protoc")
if err != nil {
return err
}
protoc_go_path, err := getToolPath("protoc-gen-go")
if err != nil {
return err
}
twirp_path, err := getToolPath("protoc-gen-twirp")
// generate model and room
cmd := exec.Command(protoc,
"--go_out", target,
"--twirp_out", target,
"--go_opt=paths=source_relative",
"--twirp_opt=paths=source_relative",
"--plugin=go="+protoc_go_path,
"--plugin=twirp="+twirp_path,
"-I=proto",
"proto/room.proto",
"proto/model.proto",
)
connectStd(cmd)
if err := cmd.Run(); err != nil {
return err
}
// generate rtc
cmd = exec.Command(protoc,
"--go_out", target,
"--go_opt=paths=source_relative",
"--plugin=go="+protoc_go_path,
"-I=proto",
"proto/rtc.proto",
)
connectStd(cmd)
if err := cmd.Run(); err != nil {
return err
}
protoChecksummer.WriteChecksum()
return nil
}
// builds LiveKit server and cli
func Build() error {
mg.Deps(Proto, generateCmd)
if !checksummer.IsChanged() {
fmt.Println("up to date")
return nil
}
fmt.Println("building...")
if err := os.MkdirAll("bin", 0755); err != nil {
return err
}
cmd := exec.Command("go", "build", "-i", "-o", "../../bin/livekit-server")
cmd.Dir = "cmd/server"
connectStd(cmd)
if err := cmd.Run(); err != nil {
return err
}
cmd = exec.Command("go", "build", "-i", "-o", "../../bin/livekit-cli")
cmd.Dir = "cmd/cli"
connectStd(cmd)
if err := cmd.Run(); err != nil {
return err
}
checksummer.WriteChecksum()
return nil
}
func Test() error {
mg.Deps(Proto)
cmd := exec.Command("go", "test", "./...")
connectStd(cmd)
return cmd.Run()
}
// cleans up builds
func Clean() {
fmt.Println("cleaning...")
os.RemoveAll("bin")
os.Remove(protoChecksumFile)
os.Remove(goChecksumFile)
}
// regenerate code
func Generate() error {
mg.Deps(installDeps)
fmt.Println("generating...")
cmd := exec.Command("go", "generate", "./...")
connectStd(cmd)
return cmd.Run()
}
// code generation for cmd subfolder. It doesn't regenerate test fixtures
func generateCmd() error {
mg.Deps(installDeps)
if !checksummer.IsChanged() {
return nil
}
fmt.Println("generating...")
cmd := exec.Command("go", "generate", "./cmd/...")
connectStd(cmd)
return cmd.Run()
}
// implicitly install deps
func installDeps() error {
return installTools(false)
}
func installTools(force bool) error {
if _, err := getToolPath("protoc"); err != nil {
return fmt.Errorf("protoc is required but is not found")
}
tools := []string{
"google.golang.org/protobuf/cmd/protoc-gen-go",
"github.com/twitchtv/twirp/protoc-gen-twirp",
"github.com/google/wire/cmd/wire",
"github.com/golang/mock/mockgen",
}
for _, t := range tools {
if err := installTool(t, force); err != nil {
return err
}
}
return nil
}
func installTool(url string, force bool) error {
name := filepath.Base(url)
if !force {
_, err := getToolPath(name)
if err == nil {
// already installed
return nil
}
}
fmt.Printf("installing %s\n", name)
cmd := exec.Command("go", "get", "-u", url)
connectStd(cmd)
if err := cmd.Run(); err != nil {
return err
}
// check
_, err := getToolPath(name)
return err
}
// helpers
func getToolPath(name string) (string, error) {
if p, err := exec.LookPath(name); err == nil {
return p, nil
}
// check under gopath
gopath := os.Getenv("GOPATH")
if gopath == "" {
gopath = build.Default.GOPATH
}
p := filepath.Join(gopath, "bin", name)
if _, err := os.Stat(p); err != nil {
return "", err
}
return p, nil
}
func connectStd(cmd *exec.Cmd) {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
// A helper checksum library that generates a fast, non-portable checksum over a directory of files
// it's designed as a quick way to bypass
type Checksummer struct {
dir string
file string
checksum string
allExts bool
extMap map[string]bool
IgnoredPaths []string
}
func NewChecksummer(dir string, checksumfile string, exts ...string) *Checksummer {
c := &Checksummer{
dir: dir,
file: checksumfile,
extMap: make(map[string]bool),
}
if len(exts) == 0 {
c.allExts = true
} else {
for _, ext := range exts {
c.extMap[ext] = true
}
}
return c
}
func (c *Checksummer) IsChanged() bool {
// default changed
if err := c.computeChecksum(); err != nil {
log.Errorf("could not compute checksum: %v", err)
return true
}
// read
existing, err := c.ReadChecksum()
if err != nil {
// may not be there
return true
}
return existing != c.checksum
}
func (c *Checksummer) ReadChecksum() (string, error) {
b, err := ioutil.ReadFile(filepath.Join(c.dir, c.file))
if err != nil {
return "", err
}
return string(b), nil
}
func (c *Checksummer) WriteChecksum() error {
if err := c.computeChecksum(); err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(c.dir, c.file), []byte(c.checksum), 0644)
}
func (c *Checksummer) computeChecksum() error {
if c.checksum != "" {
return nil
}
entries := make([]string, 0)
ignoredMap := make(map[string]bool)
for _, f := range c.IgnoredPaths {
ignoredMap[f] = true
}
err := filepath.Walk(c.dir, func(path string, info os.FileInfo, err error) error {
if path == c.dir {
return nil
}
if strings.HasPrefix(info.Name(), ".") || ignoredMap[path] {
if info.IsDir() {
return filepath.SkipDir
} else {
return nil
}
}
if info.IsDir() {
entries = append(entries, fmt.Sprintf("%s %d", path, info.ModTime().Unix()))
} else if c.allExts || c.extMap[filepath.Ext(info.Name())] {
entries = append(entries, fmt.Sprintf("%s %d %d", path, info.Size(), info.ModTime().Unix()))
}
return nil
})
if err != nil {
return err
}
sort.Strings(entries)
h := sha1.New()
for _, e := range entries {
h.Write([]byte(e))
}
c.checksum = fmt.Sprintf("%x", h.Sum(nil))
return nil
}