From 6a76b9d609fab579a3b54674f098fda06e121e49 Mon Sep 17 00:00:00 2001 From: kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com> Date: Mon, 14 Oct 2024 09:59:12 +0000 Subject: [PATCH] [feature/OFFICIALLY UNSUPPORTED] add nowasm build tag to disable building with WebAssembly (#3429) * add experimental build-tag 'nowasm' which uses local ffmpeg / ffprobe * updated experimental support message * add comment to build script explaining build tag * add nowasm build tags to moderncsqlite files --- internal/db/sqlite/driver.go | 2 +- internal/db/sqlite/driver_moderncsqlite3.go | 2 +- internal/db/sqlite/errors.go | 2 +- internal/db/sqlite/errors_moderncsqlite3.go | 2 +- internal/media/ffmpeg/args.go | 28 ++++ internal/media/ffmpeg/exec_nowasm.go | 142 ++++++++++++++++++++ internal/media/ffmpeg/ffmpeg.go | 8 +- internal/media/ffmpeg/ffmpeg_nowasm.go | 49 +++++++ internal/media/ffmpeg/ffprobe.go | 8 +- internal/media/ffmpeg/ffprobe_nowasm.go | 49 +++++++ internal/media/ffmpeg/runner.go | 9 +- internal/media/ffmpeg/wasm.go | 8 +- scripts/build.sh | 18 +-- 13 files changed, 301 insertions(+), 26 deletions(-) create mode 100644 internal/media/ffmpeg/args.go create mode 100644 internal/media/ffmpeg/exec_nowasm.go create mode 100644 internal/media/ffmpeg/ffmpeg_nowasm.go create mode 100644 internal/media/ffmpeg/ffprobe_nowasm.go diff --git a/internal/db/sqlite/driver.go b/internal/db/sqlite/driver.go index bbf0fa665..7467f75be 100644 --- a/internal/db/sqlite/driver.go +++ b/internal/db/sqlite/driver.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//go:build !moderncsqlite3 +//go:build !moderncsqlite3 && !nowasm package sqlite diff --git a/internal/db/sqlite/driver_moderncsqlite3.go b/internal/db/sqlite/driver_moderncsqlite3.go index 7cb31efea..2ba11cea4 100644 --- a/internal/db/sqlite/driver_moderncsqlite3.go +++ b/internal/db/sqlite/driver_moderncsqlite3.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//go:build moderncsqlite3 +//go:build moderncsqlite3 || nowasm package sqlite diff --git a/internal/db/sqlite/errors.go b/internal/db/sqlite/errors.go index f814fa8a4..9429a1bcd 100644 --- a/internal/db/sqlite/errors.go +++ b/internal/db/sqlite/errors.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//go:build !moderncsqlite3 +//go:build !moderncsqlite3 && !nowasm package sqlite diff --git a/internal/db/sqlite/errors_moderncsqlite3.go b/internal/db/sqlite/errors_moderncsqlite3.go index b17cebefb..c490f514e 100644 --- a/internal/db/sqlite/errors_moderncsqlite3.go +++ b/internal/db/sqlite/errors_moderncsqlite3.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//go:build moderncsqlite3 +//go:build moderncsqlite3 || nowasm package sqlite diff --git a/internal/media/ffmpeg/args.go b/internal/media/ffmpeg/args.go new file mode 100644 index 000000000..a1ed4bd35 --- /dev/null +++ b/internal/media/ffmpeg/args.go @@ -0,0 +1,28 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package ffmpeg + +import ( + "codeberg.org/gruf/go-ffmpreg/wasm" +) + +// Args encapsulates the passing of common +// configuration options to run an instance +// of a compiled WebAssembly module that is +// run in a typical CLI manner. +type Args = wasm.Args diff --git a/internal/media/ffmpeg/exec_nowasm.go b/internal/media/ffmpeg/exec_nowasm.go new file mode 100644 index 000000000..140cec5bc --- /dev/null +++ b/internal/media/ffmpeg/exec_nowasm.go @@ -0,0 +1,142 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//go:build nowasm + +package ffmpeg + +import ( + "context" + "fmt" + "io" + "io/fs" + "os/exec" + + "codeberg.org/gruf/go-ffmpreg/wasm" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/sys" +) + +func init() { + fmt.Println("!! you are using an unsupported build configuration of gotosocial with WebAssembly disabled !!") + fmt.Println("!! please do not file bug reports regarding media processing with this configuration !!") + fmt.Println("!! it is also less secure; this does not enforce version checks on ffmpeg / ffprobe versions !!") +} + +// runCmd will run 'name' with the given arguments, returning exit code or error. +func runCmd(ctx context.Context, name string, args wasm.Args) (uint32, error) { + cmd := exec.CommandContext(ctx, name, args.Args...) //nolint:gosec + + // Set provided std files. + cmd.Stdin = args.Stdin + cmd.Stdout = args.Stdout + cmd.Stderr = args.Stderr + + if args.Config != nil { + // Gather some information + // from module config func. + var cfg falseModuleConfig + _ = args.Config(&cfg) + + // Extract from conf. + cmd.Env = cfg.env + } + + // Run prepared command, catching err type. + switch err := cmd.Run(); err := err.(type) { + + // Extract code from + // any exit error type. + case *exec.ExitError: + rc := err.ExitCode() + return uint32(rc), err + + default: + return 0, err + } +} + +type falseModuleConfig struct{ env []string } + +func (cfg *falseModuleConfig) WithArgs(...string) wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithEnv(key string, value string) wazero.ModuleConfig { + cfg.env = append(cfg.env, key+"="+value) + return cfg // noop +} + +func (cfg *falseModuleConfig) WithFS(fs.FS) wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithFSConfig(wazero.FSConfig) wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithName(string) wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithStartFunctions(...string) wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithStderr(io.Writer) wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithStdin(io.Reader) wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithStdout(io.Writer) wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithWalltime(sys.Walltime, sys.ClockResolution) wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithSysWalltime() wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithNanotime(sys.Nanotime, sys.ClockResolution) wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithSysNanotime() wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithNanosleep(sys.Nanosleep) wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithOsyield(sys.Osyield) wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithSysNanosleep() wazero.ModuleConfig { + return cfg // noop +} + +func (cfg *falseModuleConfig) WithRandSource(io.Reader) wazero.ModuleConfig { + return cfg // noop +} diff --git a/internal/media/ffmpeg/ffmpeg.go b/internal/media/ffmpeg/ffmpeg.go index d33fef34e..0571c029a 100644 --- a/internal/media/ffmpeg/ffmpeg.go +++ b/internal/media/ffmpeg/ffmpeg.go @@ -15,10 +15,14 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +//go:build !nowasm + package ffmpeg import ( "context" + + "codeberg.org/gruf/go-ffmpreg/wasm" ) // ffmpegRunner limits the number of @@ -36,5 +40,7 @@ func InitFfmpeg(ctx context.Context, max int) error { // Ffmpeg runs the given arguments with an instance of ffmpeg. func Ffmpeg(ctx context.Context, args Args) (uint32, error) { - return ffmpegRunner.Run(ctx, ffmpeg, args) + return ffmpegRunner.Run(ctx, func() (uint32, error) { + return wasm.Run(ctx, runtime, ffmpeg, args) + }) } diff --git a/internal/media/ffmpeg/ffmpeg_nowasm.go b/internal/media/ffmpeg/ffmpeg_nowasm.go new file mode 100644 index 000000000..6f528da1a --- /dev/null +++ b/internal/media/ffmpeg/ffmpeg_nowasm.go @@ -0,0 +1,49 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//go:build nowasm + +package ffmpeg + +import ( + "context" + "os/exec" +) + +// ffmpegRunner limits the number of +// ffmpeg WebAssembly instances that +// may be concurrently running, in +// order to reduce memory usage. +var ffmpegRunner runner + +// InitFfmpeg looks for a local copy of ffmpeg in path, and prepares +// the runner to only allow max given concurrent running instances. +func InitFfmpeg(ctx context.Context, max int) error { + _, err := exec.LookPath("ffmpeg") + if err != nil { + return err + } + ffmpegRunner.Init(max) + return nil +} + +// Ffmpeg runs the given arguments with an instance of ffmpeg. +func Ffmpeg(ctx context.Context, args Args) (uint32, error) { + return ffmpegRunner.Run(ctx, func() (uint32, error) { + return runCmd(ctx, "ffmpeg", args) + }) +} diff --git a/internal/media/ffmpeg/ffprobe.go b/internal/media/ffmpeg/ffprobe.go index eca819b09..99d3d0563 100644 --- a/internal/media/ffmpeg/ffprobe.go +++ b/internal/media/ffmpeg/ffprobe.go @@ -15,10 +15,14 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +//go:build !nowasm + package ffmpeg import ( "context" + + "codeberg.org/gruf/go-ffmpreg/wasm" ) // ffprobeRunner limits the number of @@ -36,5 +40,7 @@ func InitFfprobe(ctx context.Context, max int) error { // Ffprobe runs the given arguments with an instance of ffprobe. func Ffprobe(ctx context.Context, args Args) (uint32, error) { - return ffprobeRunner.Run(ctx, ffprobe, args) + return ffmpegRunner.Run(ctx, func() (uint32, error) { + return wasm.Run(ctx, runtime, ffprobe, args) + }) } diff --git a/internal/media/ffmpeg/ffprobe_nowasm.go b/internal/media/ffmpeg/ffprobe_nowasm.go new file mode 100644 index 000000000..122fb53f4 --- /dev/null +++ b/internal/media/ffmpeg/ffprobe_nowasm.go @@ -0,0 +1,49 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//go:build nowasm + +package ffmpeg + +import ( + "context" + "os/exec" +) + +// ffprobeRunner limits the number of +// ffprobe WebAssembly instances that +// may be concurrently running, in +// order to reduce memory usage. +var ffprobeRunner runner + +// InitFfprobe looks for a local copy of ffprobe in path, and prepares +// the runner to only allow max given concurrent running instances. +func InitFfprobe(ctx context.Context, max int) error { + _, err := exec.LookPath("ffprobe") + if err != nil { + return err + } + ffprobeRunner.Init(max) + return nil +} + +// Ffprobe runs the given arguments with an instance of ffprobe. +func Ffprobe(ctx context.Context, args Args) (uint32, error) { + return ffprobeRunner.Run(ctx, func() (uint32, error) { + return runCmd(ctx, "ffprobe", args) + }) +} diff --git a/internal/media/ffmpeg/runner.go b/internal/media/ffmpeg/runner.go index 8c59ac752..64ff6008c 100644 --- a/internal/media/ffmpeg/runner.go +++ b/internal/media/ffmpeg/runner.go @@ -19,9 +19,6 @@ package ffmpeg import ( "context" - - "codeberg.org/gruf/go-ffmpreg/wasm" - "github.com/tetratelabs/wazero" ) // runner simply abstracts away the complexities @@ -53,7 +50,7 @@ func (r *runner) Init(n int) { // Run will attempt to pass the given compiled WebAssembly module with args to run(), waiting on // the receiving runner until a free slot is available to run an instance, (if a limit is enabled). -func (r *runner) Run(ctx context.Context, cmod wazero.CompiledModule, args Args) (uint32, error) { +func (r *runner) Run(ctx context.Context, run func() (uint32, error)) (uint32, error) { select { // Context canceled. case <-ctx.Done(): @@ -66,6 +63,6 @@ func (r *runner) Run(ctx context.Context, cmod wazero.CompiledModule, args Args) // Release slot back to pool on end. defer func() { r.pool <- struct{}{} }() - // Pass to main module runner function. - return wasm.Run(ctx, runtime, cmod, args) + // Call run. + return run() } diff --git a/internal/media/ffmpeg/wasm.go b/internal/media/ffmpeg/wasm.go index d76e9017b..4a230eec7 100644 --- a/internal/media/ffmpeg/wasm.go +++ b/internal/media/ffmpeg/wasm.go @@ -15,6 +15,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +//go:build !nowasm + package ffmpeg import ( @@ -41,12 +43,6 @@ var ( ffprobe wazero.CompiledModule ) -// Args encapsulates the passing of common -// configuration options to run an instance -// of a compiled WebAssembly module that is -// run in a typical CLI manner. -type Args = wasm.Args - // compileFfmpeg ensures the ffmpeg WebAssembly has been // pre-compiled into memory. If already compiled is a no-op. func compileFfmpeg(ctx context.Context) error { diff --git a/scripts/build.sh b/scripts/build.sh index 5b10a5493..6c1b4c50d 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -15,14 +15,16 @@ GO_GCFLAGS=${GO_GCFLAGS-} GO_BUILDTAGS="${GO_BUILDTAGS} debugenv" # Available Go build tags, with explanation, followed by benefits of enabling it: -# - kvformat: enables prettier output of log fields (slightly better performance) -# - timetzdata: embed timezone database inside binary (allow setting local time inside Docker containers, at cost of 450KB) -# - notracing: disables compiling-in otel tracing support (reduced binary size, better performance) -# - nometrics: disables compiling-in otel metrics support (reduced binary size, better performance) -# - noerrcaller: disables caller function prefix in errors (slightly better performance, at cost of err readability) -# - debug: enables /debug/pprof endpoint (adds debug, at performance cost) -# - debugenv: enables /debug/pprof endpoint if DEBUG=1 env during runtime (adds debug, at performance cost) -# - moderncsqlite3: reverts to using the C-to-Go transpiled SQLite driver (disables the WASM-based SQLite driver) +# - kvformat: enables prettier output of log fields (slightly better performance) +# - timetzdata: embed timezone database inside binary (allow setting local time inside Docker containers, at cost of 450KB) +# - notracing: disables compiling-in otel tracing support (reduced binary size, better performance) +# - nometrics: disables compiling-in otel metrics support (reduced binary size, better performance) +# - noerrcaller: disables caller function prefix in errors (slightly better performance, at cost of err readability) +# - debug: enables /debug/pprof endpoint (adds debug, at performance cost) +# - debugenv: enables /debug/pprof endpoint if DEBUG=1 env during runtime (adds debug, at performance cost) +# - moderncsqlite3: reverts to using the C-to-Go transpiled SQLite driver (disables the WASM-based SQLite driver) +# - nowasm: [UNSUPPORTED] removes all WebAssembly from builds including +# ffmpeg, ffprobe and SQLite (instead falling back to modernc). log_exec env CGO_ENABLED=0 go build -trimpath -v \ -tags "${GO_BUILDTAGS}" \ -ldflags="${GO_LDFLAGS}" \