add build script
This commit is contained in:
@@ -25,3 +25,4 @@ go.work.sum
|
|||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
.build/
|
||||||
|
|||||||
@@ -0,0 +1,476 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
projectDir = func() string {
|
||||||
|
// go run cmd/build/main.go — __file__ is in cmd/build/, project root is ../../
|
||||||
|
exe, _ := os.Getwd()
|
||||||
|
return exe
|
||||||
|
}()
|
||||||
|
buildDir = filepath.Join(projectDir, ".build")
|
||||||
|
vendorDir = filepath.Join(projectDir, "vendor")
|
||||||
|
gav1dDir = filepath.Join(buildDir, "dav1d")
|
||||||
|
ffmpegDir = filepath.Join(buildDir, "ffmpeg")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
github := os.Getenv("GITHUB_WORKSPACE")
|
||||||
|
if github != "" {
|
||||||
|
projectDir = github
|
||||||
|
buildDir = filepath.Join(projectDir, ".build")
|
||||||
|
vendorDir = filepath.Join(projectDir, "vendor")
|
||||||
|
gav1dDir = filepath.Join(buildDir, "dav1d")
|
||||||
|
ffmpegDir = filepath.Join(buildDir, "ffmpeg")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type buildTarget struct {
|
||||||
|
name string
|
||||||
|
goos string
|
||||||
|
goarch string
|
||||||
|
dav1dCrossFile string
|
||||||
|
ffmpegExtraArgs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func availableTargets() []buildTarget {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
return []buildTarget{
|
||||||
|
{name: "linux_amd64", goos: "linux", goarch: "amd64"},
|
||||||
|
{
|
||||||
|
name: "linux_arm64",
|
||||||
|
goos: "linux",
|
||||||
|
goarch: "arm64",
|
||||||
|
dav1dCrossFile: "cross_linux_arm64.ini",
|
||||||
|
ffmpegExtraArgs: []string{
|
||||||
|
"--enable-cross-compile",
|
||||||
|
"--arch=aarch64",
|
||||||
|
"--target-os=linux",
|
||||||
|
"--cross-prefix=aarch64-linux-gnu-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "windows_amd64",
|
||||||
|
goos: "windows",
|
||||||
|
goarch: "amd64",
|
||||||
|
dav1dCrossFile: "cross_windows_amd64.ini",
|
||||||
|
ffmpegExtraArgs: []string{
|
||||||
|
"--enable-cross-compile",
|
||||||
|
"--arch=x86_64",
|
||||||
|
"--target-os=mingw32",
|
||||||
|
"--cross-prefix=x86_64-w64-mingw32-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case "darwin":
|
||||||
|
return []buildTarget{
|
||||||
|
{name: "darwin_arm64", goos: "darwin", goarch: "arm64"},
|
||||||
|
{
|
||||||
|
name: "darwin_amd64",
|
||||||
|
goos: "darwin",
|
||||||
|
goarch: "amd64",
|
||||||
|
dav1dCrossFile: "cross_darwin_amd64.ini",
|
||||||
|
ffmpegExtraArgs: []string{
|
||||||
|
"--enable-cross-compile",
|
||||||
|
"--arch=x86_64",
|
||||||
|
"--target-os=darwin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case "windows":
|
||||||
|
return []buildTarget{
|
||||||
|
{name: "windows_amd64", goos: "windows", goarch: "amd64"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNative(t buildTarget) bool {
|
||||||
|
return t.goos == runtime.GOOS && t.goarch == runtime.GOARCH
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkTool(tool string) bool {
|
||||||
|
_, err := exec.LookPath(tool)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkBuildDeps(target buildTarget) []string {
|
||||||
|
var missing []string
|
||||||
|
cc := "gcc"
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
cc = "clang"
|
||||||
|
}
|
||||||
|
tools := []string{cc, "meson", "ninja", "pkg-config"}
|
||||||
|
if isNative(target) {
|
||||||
|
tools = append(tools, "nasm")
|
||||||
|
}
|
||||||
|
for _, t := range tools {
|
||||||
|
if !checkTool(t) {
|
||||||
|
missing = append(missing, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isNative(target) {
|
||||||
|
switch target.name {
|
||||||
|
case "linux_arm64":
|
||||||
|
if !checkTool("aarch64-linux-gnu-gcc") {
|
||||||
|
missing = append(missing, "aarch64-linux-gnu-gcc")
|
||||||
|
}
|
||||||
|
case "windows_amd64":
|
||||||
|
if !checkTool("x86_64-w64-mingw32-gcc") {
|
||||||
|
missing = append(missing, "x86_64-w64-mingw32-gcc")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return missing
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(dir, name string, args ...string) error {
|
||||||
|
cmd := exec.Command(name, args...)
|
||||||
|
cmd.Dir = dir
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
fmt.Printf("\n>>> %s %v (in %s)\n", name, args, dir)
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFile(path, content string) error {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, []byte(content), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
s, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open %s: %w", src, err)
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create %s: %w", dst, err)
|
||||||
|
}
|
||||||
|
defer d.Close()
|
||||||
|
if _, err := io.Copy(d, s); err != nil {
|
||||||
|
return fmt.Errorf("copy %s -> %s: %w", src, dst, err)
|
||||||
|
}
|
||||||
|
fmt.Printf(" %s -> %s\n", filepath.Base(src), dst)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func artifactExists(target buildTarget) bool {
|
||||||
|
dest := filepath.Join(vendorDir, target.name)
|
||||||
|
for _, lib := range []string{"libavcodec.a", "libavformat.a", "libavutil.a", "libswresample.a", "libdav1d.a"} {
|
||||||
|
if _, err := os.Stat(filepath.Join(dest, lib)); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneIfMissing(dir, url string) error {
|
||||||
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
|
fmt.Printf("cloning %s into %s...\n", url, dir)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dir), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return run(filepath.Dir(dir), "git", "clone", "--depth", "1", url, dir)
|
||||||
|
}
|
||||||
|
fmt.Printf("already exists: %s\n", dir)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDav1d(target buildTarget) error {
|
||||||
|
name := target.name
|
||||||
|
fmt.Printf("\n=== dav1d %s ===\n", name)
|
||||||
|
|
||||||
|
targetBuildDir := filepath.Join(gav1dDir, "build_"+name)
|
||||||
|
installDir := filepath.Join(buildDir, "dav1d_install_"+name)
|
||||||
|
_ = os.RemoveAll(targetBuildDir)
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"setup", targetBuildDir,
|
||||||
|
"--default-library=static",
|
||||||
|
"--buildtype=release",
|
||||||
|
"-Denable_tools=false",
|
||||||
|
"-Denable_tests=false",
|
||||||
|
"--prefix=" + installDir,
|
||||||
|
}
|
||||||
|
if isNative(target) {
|
||||||
|
args = append(args, "-Denable_asm=true")
|
||||||
|
} else {
|
||||||
|
args = append(args, "-Denable_asm=false")
|
||||||
|
}
|
||||||
|
if target.dav1dCrossFile != "" {
|
||||||
|
args = append(args, "--cross-file="+filepath.Join(buildDir, target.dav1dCrossFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := run(gav1dDir, "meson", args...); err != nil {
|
||||||
|
return fmt.Errorf("meson setup dav1d %s: %w", name, err)
|
||||||
|
}
|
||||||
|
if err := run(gav1dDir, "ninja", "-C", targetBuildDir); err != nil {
|
||||||
|
return fmt.Errorf("ninja dav1d %s: %w", name, err)
|
||||||
|
}
|
||||||
|
if err := run(gav1dDir, "ninja", "-C", targetBuildDir, "install"); err != nil {
|
||||||
|
return fmt.Errorf("ninja install dav1d %s: %w", name, err)
|
||||||
|
}
|
||||||
|
return copyFile(
|
||||||
|
filepath.Join(installDir, "lib", "libdav1d.a"),
|
||||||
|
filepath.Join(vendorDir, name, "libdav1d.a"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFFmpeg(target buildTarget) error {
|
||||||
|
name := target.name
|
||||||
|
fmt.Printf("\n=== ffmpeg %s ===\n", name)
|
||||||
|
|
||||||
|
installDir := filepath.Join(buildDir, "ffmpeg_install_"+name)
|
||||||
|
dav1dInstall := filepath.Join(buildDir, "dav1d_install_"+name)
|
||||||
|
if err := os.MkdirAll(installDir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
run(ffmpegDir, "make", "distclean")
|
||||||
|
|
||||||
|
extraCFlags := "-I" + filepath.Join(dav1dInstall, "include")
|
||||||
|
extraLDFlags := "-L" + filepath.Join(dav1dInstall, "lib")
|
||||||
|
if target.goos == "darwin" && target.goarch == "amd64" {
|
||||||
|
extraCFlags += " -arch x86_64"
|
||||||
|
extraLDFlags += " -arch x86_64"
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"--prefix=" + installDir,
|
||||||
|
"--disable-everything",
|
||||||
|
"--disable-programs",
|
||||||
|
"--disable-doc",
|
||||||
|
"--disable-network",
|
||||||
|
"--disable-avdevice",
|
||||||
|
"--disable-avfilter",
|
||||||
|
"--disable-swscale",
|
||||||
|
"--disable-vaapi",
|
||||||
|
"--disable-vdpau",
|
||||||
|
"--disable-bzlib",
|
||||||
|
"--disable-xlib",
|
||||||
|
"--disable-libdrm",
|
||||||
|
"--disable-hwaccels",
|
||||||
|
"--enable-libdav1d",
|
||||||
|
"--enable-avcodec",
|
||||||
|
"--enable-avformat",
|
||||||
|
"--enable-avutil",
|
||||||
|
"--enable-swresample",
|
||||||
|
"--enable-static",
|
||||||
|
"--disable-shared",
|
||||||
|
"--enable-pic",
|
||||||
|
"--enable-decoder=h264,hevc,libdav1d,vp9,vp8,mpeg4,mp3,aac,opus,vorbis,flac,pcm_s16le,pcm_s16be,pcm_f32le",
|
||||||
|
"--enable-demuxer=mov,mp4,matroska,webm,avi,flv,ogg,mp3,aac,flac,ivf,h264,hevc,mpegts",
|
||||||
|
"--enable-parser=h264,hevc,av1,vp9,vp8,mpeg4video,mp3,aac,opus,vorbis,flac",
|
||||||
|
"--enable-protocol=file,pipe",
|
||||||
|
"--enable-bsf=h264_mp4toannexb,hevc_mp4toannexb,extract_extradata",
|
||||||
|
"--pkg-config=pkg-config",
|
||||||
|
"--pkg-config-flags=--static",
|
||||||
|
"--extra-cflags=" + extraCFlags,
|
||||||
|
"--extra-ldflags=" + extraLDFlags,
|
||||||
|
}
|
||||||
|
args = append(args, target.ffmpegExtraArgs...)
|
||||||
|
|
||||||
|
pkgPath := filepath.Join(dav1dInstall, "lib", "pkgconfig")
|
||||||
|
filtered := []string{}
|
||||||
|
for _, e := range os.Environ() {
|
||||||
|
if !strings.HasPrefix(e, "PKG_CONFIG_PATH=") &&
|
||||||
|
!strings.HasPrefix(e, "PKG_CONFIG_LIBDIR=") {
|
||||||
|
filtered = append(filtered, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(filepath.Join(ffmpegDir, "configure"), args...)
|
||||||
|
cmd.Dir = ffmpegDir
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = append(filtered,
|
||||||
|
"PKG_CONFIG_PATH="+pkgPath,
|
||||||
|
"PKG_CONFIG_LIBDIR="+pkgPath,
|
||||||
|
)
|
||||||
|
fmt.Printf("\n>>> ./configure ... (in %s)\n", ffmpegDir)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("configure ffmpeg %s: %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nproc := fmt.Sprintf("%d", runtime.NumCPU())
|
||||||
|
if err := run(ffmpegDir, "make", "-j"+nproc); err != nil {
|
||||||
|
return fmt.Errorf("make ffmpeg %s: %w", name, err)
|
||||||
|
}
|
||||||
|
if err := run(ffmpegDir, "make", "install"); err != nil {
|
||||||
|
return fmt.Errorf("make install ffmpeg %s: %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := filepath.Join(vendorDir, name)
|
||||||
|
for _, lib := range []string{"libavcodec.a", "libavformat.a", "libavutil.a", "libswresample.a"} {
|
||||||
|
if err := copyFile(filepath.Join(installDir, "lib", lib), filepath.Join(dest, lib)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstBuiltTarget() string {
|
||||||
|
for _, t := range availableTargets() {
|
||||||
|
if artifactExists(t) {
|
||||||
|
return t.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return availableTargets()[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyHeaders() error {
|
||||||
|
fmt.Println("\n=== copying headers ===")
|
||||||
|
first := firstBuiltTarget()
|
||||||
|
|
||||||
|
src := filepath.Join(buildDir, "ffmpeg_install_"+first, "include")
|
||||||
|
dst := filepath.Join(vendorDir, "include")
|
||||||
|
filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil || info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rel, _ := filepath.Rel(src, path)
|
||||||
|
return copyFile(path, filepath.Join(dst, rel))
|
||||||
|
})
|
||||||
|
|
||||||
|
srcDav1d := filepath.Join(buildDir, "dav1d_install_"+first, "include", "dav1d")
|
||||||
|
dstDav1d := filepath.Join(vendorDir, "include", "dav1d")
|
||||||
|
filepath.Walk(srcDav1d, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil || info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rel, _ := filepath.Rel(srcDav1d, path)
|
||||||
|
return copyFile(path, filepath.Join(dstDav1d, rel))
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCrossFiles() error {
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
pkgConfigPath, _ := exec.LookPath("pkg-config")
|
||||||
|
wrapperPath := filepath.Join(buildDir, "clang_x86_64.sh")
|
||||||
|
if err := writeFile(wrapperPath, "#!/bin/sh\nexec clang -arch x86_64 \"$@\"\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Chmod(wrapperPath, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeFile(
|
||||||
|
filepath.Join(buildDir, "cross_darwin_amd64.ini"),
|
||||||
|
fmt.Sprintf(`[binaries]
|
||||||
|
c = '%s'
|
||||||
|
ar = 'ar'
|
||||||
|
strip = 'strip'
|
||||||
|
pkg-config = '%s'
|
||||||
|
|
||||||
|
[built-in options]
|
||||||
|
c_args = ['-arch', 'x86_64']
|
||||||
|
c_link_args = ['-arch', 'x86_64']
|
||||||
|
|
||||||
|
[host_machine]
|
||||||
|
system = 'darwin'
|
||||||
|
cpu_family = 'x86_64'
|
||||||
|
cpu = 'x86_64'
|
||||||
|
endian = 'little'
|
||||||
|
`, wrapperPath, pkgConfigPath)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writeFile(filepath.Join(buildDir, "cross_linux_arm64.ini"), `[binaries]
|
||||||
|
c = 'aarch64-linux-gnu-gcc'
|
||||||
|
ar = 'aarch64-linux-gnu-ar'
|
||||||
|
strip = 'aarch64-linux-gnu-strip'
|
||||||
|
pkgconfig = 'pkg-config'
|
||||||
|
|
||||||
|
[host_machine]
|
||||||
|
system = 'linux'
|
||||||
|
cpu_family = 'aarch64'
|
||||||
|
cpu = 'aarch64'
|
||||||
|
endian = 'little'
|
||||||
|
`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeFile(filepath.Join(buildDir, "cross_windows_amd64.ini"), `[binaries]
|
||||||
|
c = 'x86_64-w64-mingw32-gcc'
|
||||||
|
ar = 'x86_64-w64-mingw32-ar'
|
||||||
|
strip = 'x86_64-w64-mingw32-strip'
|
||||||
|
pkgconfig = 'pkg-config'
|
||||||
|
|
||||||
|
[host_machine]
|
||||||
|
system = 'windows'
|
||||||
|
cpu_family = 'x86_64'
|
||||||
|
cpu = 'x86_64'
|
||||||
|
endian = 'little'
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := os.MkdirAll(buildDir, 0755); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cloneIfMissing(ffmpegDir, "https://m8sh.su/x/ffmpeg"); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := cloneIfMissing(gav1dDir, "https://m8sh.su/x/dav1d"); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writeCrossFiles(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetsToBuild []buildTarget
|
||||||
|
for _, t := range availableTargets() {
|
||||||
|
missing := checkBuildDeps(t)
|
||||||
|
if len(missing) > 0 {
|
||||||
|
fmt.Printf("SKIP %s (missing deps: %v)\n", t.name, missing)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
targetsToBuild = append(targetsToBuild, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range targetsToBuild {
|
||||||
|
if artifactExists(t) {
|
||||||
|
fmt.Printf("\nSKIP %s (artifacts already exist)\n", t.name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := buildDav1d(t); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := buildFFmpeg(t); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyHeaders(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("\n=== all done ===")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user