diff --git a/go.mod b/go.mod index 8a25f44..28e80fa 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/yeslayla/godot-build-tools go 1.20 require ( + github.com/BurntSushi/toml v1.3.2 github.com/sethvargo/go-envconfig v0.9.0 // indirect github.com/sethvargo/go-githubactions v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index 757224e..8a03960 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/sethvargo/go-envconfig v0.9.0 h1:Q6FQ6hVEeTECULvkJZakq3dZMeBQ3JUpcKMfPQbKMDE= github.com/sethvargo/go-envconfig v0.9.0/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0= github.com/sethvargo/go-githubactions v1.1.0 h1:mg03w+b+/s5SMS298/2G6tHv8P0w0VhUFaqL1THIqzY= diff --git a/internal/build_config.go b/internal/build_config.go new file mode 100644 index 0000000..6dc1077 --- /dev/null +++ b/internal/build_config.go @@ -0,0 +1,54 @@ +package internal + +import ( + "io/ioutil" + "os" + + "github.com/BurntSushi/toml" + "github.com/yeslayla/godot-build-tools/logging" +) + +const defaultGodotVersion = "4.1.3" +const defaultGodotRelease = "stable" + +type BuildConfig struct { + Godot BuildConfigGodot `toml:"godot"` +} + +type BuildConfigGodot struct { + Version string `toml:"version"` + Release string `toml:"release"` +} + +func LoadBuildConfig(logger logging.Logger) BuildConfig { + config := BuildConfig{} + + content, err := ioutil.ReadFile(".godot-build.toml") + if err != nil { + if os.IsNotExist(err) { + logger.Errorf("Build config not found, please run `gbt init`") + os.Exit(1) + } else { + logger.Errorf("Failed to read build config: %s", err) + } + return config + } + + _, err = toml.Decode(string(content), &config) + if err != nil { + logger.Errorf("Failed to parse build config: %s", err) + return config + } + + if config.Godot.Release == "" { + logger.Warnf("Godot release not specified, defaulting to %s", defaultGodotRelease) + config.Godot.Release = defaultGodotRelease + } + + if config.Godot.Version == "" { + logger.Warnf("Godot version not specified, defaulting to %s", defaultGodotVersion) + config.Godot.Version = defaultGodotVersion + } + + return config +} diff --git a/internal/downloader.go b/internal/downloader.go index 143aeb4..4f6c2b5 100644 --- a/internal/downloader.go +++ b/internal/downloader.go @@ -26,6 +26,20 @@ type Downloader struct { logger logging.Logger } +// DefaultBinDir returns the default bin directory to install Godot to for the given target OS. +func DefaultBinDir(targetOS TargetOS) string { + switch targetOS { + case TargetOSLinux: + home, _ := os.UserHomeDir() + return filepath.Join(home, "/.local/bin") + case TargetOSWindows: + return "C:\\Program Files (x86)\\Godot" + case TargetOSMacOS: + return "/Applications/Godot" + } + return "" +} + func NewDownloader(targetOS TargetOS, logger logging.Logger, options *DownloaderOptions) *Downloader { var url string = options.DownloadRepositoryURL if url == "" { @@ -33,15 +47,7 @@ func NewDownloader(targetOS TargetOS, logger logging.Logger, options *Downloader } var binDir string = options.BinDir if binDir == "" { - switch targetOS { - case TargetOSLinux: - home, _ := os.UserHomeDir() - binDir = filepath.Join(home, "/.local/bin") - case TargetOSWindows: - binDir = "C:\\Program Files (x86)\\Godot" - case TargetOSMacOS: - binDir = "/Applications/Godot" - } + DefaultBinDir(targetOS) } return &Downloader{ @@ -51,26 +57,29 @@ func NewDownloader(targetOS TargetOS, logger logging.Logger, options *Downloader } } -func getRemoteFileFormat(targetOS TargetOS, version string) string { +// getRemoteFileName returns the name of the Godot package file for the given target OS, version and release. +func getRemoteFileName(targetOS TargetOS, version string, release string) string { switch targetOS { case TargetOSLinux: if version[0] == '3' { - return "Godot_v%s-%s_x11.64.zip" + return fmt.Sprintf("Godot_v%s-%s_x11.64.zip", version, release) } - return "Godot_v%s-%s_linux.x86_64.zip" + return fmt.Sprintf("Godot_v%s-%s_linux.x86_64.zip", version, release) case TargetOSWindows: - return "Godot_v%s-%s_win64.exe.zip" + return fmt.Sprintf("Godot_v%s-%s_win64.exe.zip", version, release) case TargetOSMacOS: - return "Godot_v%s-%s_macos.universal.zip" + return fmt.Sprintf("Godot_v%s-%s_macos.universal.zip", version, release) } return "" } +// DownloadGodot downloads the Godot package for the given target OS, version, and release. func (d *Downloader) DownloadGodot(targetOS TargetOS, version string, release string) (string, error) { - var fileName string = fmt.Sprintf(getRemoteFileFormat(targetOS, version), version, release) + var fileName string = getRemoteFileName(targetOS, version, release) + // Create output file tempDir, _ := os.MkdirTemp("", "godot-build-tools") outFile := filepath.Join(tempDir, fileName) out, err := os.Create(outFile) @@ -79,6 +88,7 @@ func (d *Downloader) DownloadGodot(targetOS TargetOS, version string, release st } defer out.Close() + // Calculate download URL downloadURL, err := url.Parse(d.downloadRepositoryURL) if err != nil { return "", fmt.Errorf("failed to parse download repository URL: %s", err) @@ -92,12 +102,14 @@ func (d *Downloader) DownloadGodot(targetOS TargetOS, version string, release st downloadURL.Path = path.Join(downloadURL.Path, fileName) d.logger.Debugf("Download URL: %s", downloadURL.String()) + // Download Godot package resp, err := http.Get(downloadURL.String()) if err != nil { return "", fmt.Errorf("failed to download Godot: %s", err) } defer resp.Body.Close() + // Write Godot package to output file _, err = io.Copy(out, resp.Body) if err != nil { return "", fmt.Errorf("failed to write Godot package to output file: %s", err) @@ -106,6 +118,26 @@ func (d *Downloader) DownloadGodot(targetOS TargetOS, version string, release st return outFile, nil } +// isTargetOSBin returns true if the given file name is a binary for the given target OS. +func isTargetOSBin(targetOS TargetOS, fileName string) bool { + switch targetOS { + case TargetOSLinux: + if path.Ext(fileName) == ".x86_64" || path.Ext(fileName) == ".64" { + return true + } + case TargetOSWindows: + if path.Ext(fileName) == ".exe" { + return true + } + case TargetOSMacOS: + if path.Ext(fileName) == ".universal" { + return true + } + } + + return false +} + func (d *Downloader) UnzipGodot(targetOS TargetOS, godotPackage string) (string, error) { files, err := utils.Unzip(godotPackage) if err != nil { @@ -114,19 +146,8 @@ func (d *Downloader) UnzipGodot(targetOS TargetOS, godotPackage string) (string, // Look for godot binary for _, file := range files { - switch targetOS { - case TargetOSLinux: - if path.Ext(file) == ".x86_64" || path.Ext(file) == ".64" { - return file, nil - } - case TargetOSWindows: - if path.Ext(file) == ".exe" { - return file, nil - } - case TargetOSMacOS: - if path.Ext(file) == ".universal" { - return file, nil - } + if isTargetOSBin(targetOS, file) { + return file, nil } } diff --git a/internal/flags.go b/internal/flags.go new file mode 100644 index 0000000..3730971 --- /dev/null +++ b/internal/flags.go @@ -0,0 +1,44 @@ +package internal + +import ( + "flag" + "strings" + + "github.com/yeslayla/godot-build-tools/logging" +) + +type BuildFlags struct { + stepsRaw string + DebugLog bool +} + +// Steps returns the steps to run as a slice of strings +func (f *BuildFlags) Steps() []string { + return strings.Split(f.stepsRaw, ",") +} + +// HasStep returns true if the given step is in the list of steps to run +func (f *BuildFlags) HasStep(step string) bool { + steps := f.Steps() + for _, s := range steps { + if s == step { + return true + } + } + return false +} + +// Parse parses the flags +func (f *BuildFlags) Parse() { + flag.Parse() +} + +// NewBuildFlags creates a new BuildFlags instance +func NewBuildFlags(logger logging.Logger) *BuildFlags { + flags := &BuildFlags{} + + flag.StringVar(&flags.stepsRaw, "steps", "godot-setup", "Comma-separated list of build steps to run") + flag.BoolVar(&flags.DebugLog, "verbose", false, "Enable debug logging") + + return flags +} diff --git a/internal/godot4.go b/internal/godot.go similarity index 50% rename from internal/godot4.go rename to internal/godot.go index 7163d4c..28a206b 100644 --- a/internal/godot4.go +++ b/internal/godot.go @@ -2,45 +2,45 @@ package internal import "strings" -type Godot4ArgBuilder struct { +type DefaultGodotArgBuilder struct { args []string } -func NewGodot4ArgBuilder(projectDir string) GodotArgBuilder { - return &Godot4ArgBuilder{ +func NewGodotArgBuilder(projectDir string) GodotArgBuilder { + return &DefaultGodotArgBuilder{ args: []string{"--path", projectDir}, } } -func (b *Godot4ArgBuilder) AddHeadlessFlag() { +func (b *DefaultGodotArgBuilder) AddHeadlessFlag() { b.args = append(b.args, "--headless") } -func (b *Godot4ArgBuilder) AddDebugFlag() { +func (b *DefaultGodotArgBuilder) AddDebugFlag() { b.args = append(b.args, "--debug") } -func (b *Godot4ArgBuilder) AddVerboseFlag() { +func (b *DefaultGodotArgBuilder) AddVerboseFlag() { b.args = append(b.args, "--verbose") } -func (b *Godot4ArgBuilder) AddQuietFlag() { +func (b *DefaultGodotArgBuilder) AddQuietFlag() { b.args = append(b.args, "--quiet") } -func (b *Godot4ArgBuilder) AddDumpGDExtensionInterfaceFlag() { +func (b *DefaultGodotArgBuilder) AddDumpGDExtensionInterfaceFlag() { b.args = append(b.args, "--dump-gdextension-interface") } -func (b *Godot4ArgBuilder) AddDumpExtensionApiFlag() { +func (b *DefaultGodotArgBuilder) AddDumpExtensionApiFlag() { b.args = append(b.args, "--dump-extension-api") } -func (b *Godot4ArgBuilder) AddCheckOnlyFlag() { +func (b *DefaultGodotArgBuilder) AddCheckOnlyFlag() { b.args = append(b.args, "--check-only") } -func (b *Godot4ArgBuilder) AddExportFlag(exportType ExportType) { +func (b *DefaultGodotArgBuilder) AddExportFlag(exportType ExportType) { switch exportType { case ExportTypeRelease: b.args = append(b.args, "--export") @@ -51,6 +51,6 @@ func (b *Godot4ArgBuilder) AddExportFlag(exportType ExportType) { } } -func (b *Godot4ArgBuilder) GenerateArgs() string { +func (b *DefaultGodotArgBuilder) GenerateArgs() string { return strings.Join(b.args, " ") } diff --git a/internal/os.go b/internal/os.go index 1f5791a..3323104 100644 --- a/internal/os.go +++ b/internal/os.go @@ -1,5 +1,7 @@ package internal +import "runtime" + type TargetOS uint8 const ( @@ -7,3 +9,31 @@ const ( TargetOSWindows TargetOSMacOS ) + +func (t TargetOS) String() string { + switch t { + case TargetOSLinux: + return "linux" + case TargetOSWindows: + return "windows" + case TargetOSMacOS: + return "macos" + } + return "" +} + +func NewTargetOSFromRuntime(GOOSRuntime string) TargetOS { + switch GOOSRuntime { + case "linux": + return TargetOSLinux + case "windows": + return TargetOSWindows + case "darwin": + return TargetOSMacOS + } + return TargetOSLinux +} + +func CurrentTargetOS() TargetOS { + return NewTargetOSFromRuntime(runtime.GOOS) +} diff --git a/main.go b/main.go index 684ffb6..38fdf4b 100644 --- a/main.go +++ b/main.go @@ -1,51 +1,31 @@ package main import ( - "os" - "runtime" - "github.com/yeslayla/godot-build-tools/internal" "github.com/yeslayla/godot-build-tools/logging" + "github.com/yeslayla/godot-build-tools/steps" ) func main() { logger := logging.NewLogger(&logging.LoggerOptions{}) - var targetOS internal.TargetOS - switch runtime.GOOS { - case "linux": - targetOS = internal.TargetOSLinux - case "windows": - targetOS = internal.TargetOSWindows - case "darwin": - targetOS = internal.TargetOSMacOS + flags := internal.NewBuildFlags(logger) + flags.Parse() + + if flags.DebugLog { + logger = logging.NewLogger(&logging.LoggerOptions{ + Debug: true, + }) } - GodotSetup(logger, targetOS, "3.3.2", "stable") + buildConfig := internal.LoadBuildConfig(logger) + + var targetOS internal.TargetOS = internal.CurrentTargetOS() + + if flags.HasStep("godot-setup") { + steps.GodotSetup(logger, targetOS, buildConfig.Godot.Version, buildConfig.Godot.Release) + } else { + logger.Debugf("Skipping godot-setup step") + } } - -func GodotSetup(logger logging.Logger, targetOS internal.TargetOS, version string, release string) (string, bool) { - logger.StartGroup("Godot Setup") - defer logger.EndGroup() - downloader := internal.NewDownloader(internal.TargetOSLinux, logger, &internal.DownloaderOptions{}) - - logger.Infof("Downloading Godot") - godotPackage, err := downloader.DownloadGodot(internal.TargetOSLinux, version, release) - if err != nil { - logger.Errorf("Failed to download Godot: %s", err) - return "", false - } - defer os.Remove(godotPackage) - logger.Infof("Godot package: %s", godotPackage) - - logger.Infof("Installing Godot") - godotBin, err := downloader.InstallGodot(godotPackage, internal.TargetOSLinux, version, release) - if err != nil { - logger.Errorf("Failed to install Godot: %s", err) - return "", false - } - logger.Infof("Godot binary: %s", godotBin) - - return godotBin, true -} diff --git a/steps/godot_setup.go b/steps/godot_setup.go new file mode 100644 index 0000000..fcc8b68 --- /dev/null +++ b/steps/godot_setup.go @@ -0,0 +1,33 @@ +package steps + +import ( + "os" + + "github.com/yeslayla/godot-build-tools/internal" + "github.com/yeslayla/godot-build-tools/logging" +) + +func GodotSetup(logger logging.Logger, targetOS internal.TargetOS, version string, release string) (string, bool) { + logger.StartGroup("Godot Setup") + defer logger.EndGroup() + downloader := internal.NewDownloader(internal.TargetOSLinux, logger, &internal.DownloaderOptions{}) + + logger.Infof("Downloading Godot") + godotPackage, err := downloader.DownloadGodot(internal.TargetOSLinux, version, release) + if err != nil { + logger.Errorf("Failed to download Godot: %s", err) + return "", false + } + defer os.Remove(godotPackage) + logger.Infof("Godot package: %s", godotPackage) + + logger.Infof("Installing Godot") + godotBin, err := downloader.InstallGodot(godotPackage, internal.TargetOSLinux, version, release) + if err != nil { + logger.Errorf("Failed to install Godot: %s", err) + return "", false + } + logger.Infof("Godot binary: %s", godotBin) + + return godotBin, true +}