我們將製作一個像 make 這樣的工具,我們可以使用像這樣的簡單 yaml 檔案來運行任務。
tasks: build: description: "compile the project" command: "go build main.go" dependencies: [test] test: description: "run unit tests" command: "go test -v ./..."
讓我們開始吧,首先我們需要概述行動過程。我們已經定義了任務文件架構。我們可以使用 json 來代替 yaml,但為了這個項目,我們將使用 yml 檔。
從檔案中我們可以看到,我們需要一個結構來儲存單一任務,以及在繼續主任務之前運行依賴任務的方法。讓我們從啟動我們的專案開始。建立一個新資料夾並運行:
go mod init github.com/vishaaxl/mommy
您可以隨意命名您的項目,我將使用「媽媽」的名字。我們還需要安裝一些套件來處理 yaml 檔案 - 基本上將它們轉換為地圖物件。繼續安裝以下軟體包。
go get gopkg.in/yaml.v3
接下來建立一個新的 main.go 檔案並從定義「Task」結構開始。
package main import ( "gopkg.in/yaml.v3" ) // Task defines the structure of a task in the configuration file. // Each task has a description, a command to run, and a list of dependencies // (other tasks that need to be completed before this task). type Task struct { Description string `yaml:"description"` // A brief description of the task. Command string `yaml:"command"` // The shell command to execute for the task. Dependencies []string `yaml:"dependencies"` // List of tasks that need to be completed before this task. }
這個是非常不言自明的。這將保存每個單獨任務的價值。接下來,我們還需要一個結構體來儲存任務清單並將 .yaml 檔案的內容載入到這個新物件中。
// Config represents the entire configuration file, // which contains a map of tasks by name. type Config struct { Tasks map[string]Task `yaml:"tasks"` // A map of task names to task details. } // loadConfig reads and parses the configuration file (e.g., Makefile.yaml), // and returns a Config struct containing the tasks and their details. func loadConfig(filename string) (Config, error) { // Read the content of the config file. data, err := os.ReadFile(filename) if err != nil { return Config{}, err } // Unmarshal the YAML data into a Config struct. var config Config err = yaml.Unmarshal(data, &config) if err != nil { return Config{}, err } return config, nil }
接下來我們需要建立一個執行單一任務的函數。我們將使用 os/exec 模組在 shell 中執行任務。 在 Golang 中,os/exec 套件提供了一種執行 shell 指令和外部程式的方法。
// executeTask recursively executes the specified task and its dependencies. // It first ensures that all dependencies are executed before running the current task's command. func executeTask(taskName string, tasks map[string]Task, executed map[string]bool) error { // If the task has already been executed, skip it. if executed[taskName] { return nil } // Get the task details from the tasks map. task, exists := tasks[taskName] if !exists { return fmt.Errorf("task %s not found", taskName) } // First, execute all the dependencies of this task. for _, dep := range task.Dependencies { // Recursively execute each dependency. if err := executeTask(dep, tasks, executed); err != nil { return err } } // Now that dependencies are executed, run the task's command. fmt.Printf("Running task: %s\n", taskName) fmt.Printf("Command: %s\n", task.Command) // Execute the task's command using the shell (sh -c allows for complex shell commands). cmd := exec.Command("sh", "-c", task.Command) cmd.Stdout = os.Stdout // Direct standard output to the terminal. cmd.Stderr = os.Stderr // Direct error output to the terminal. // Run the command and check for any errors. if err := cmd.Run(); err != nil { return fmt.Errorf("failed to execute command %s: %v", task.Command, err) } // Mark the task as executed. executed[taskName] = true return nil }
現在我們擁有了程式的所有建構塊,我們可以在主函數中使用它們來載入設定檔並開始自動化。我們將使用 flag 套件來讀取命令列標誌。
func main() { // Define command-line flags configFile := flag.String("f", "Mommy.yaml", "Path to the configuration file") // Path to the config file (defaults to Makefile.yaml) taskName := flag.String("task", "", "Task to execute") // The task to execute (required flag) // Parse the flags flag.Parse() // Check if the task flag is provided if *taskName == "" { fmt.Println("Error: Please specify a task using -task flag.") os.Exit(1) // Exit if no task is provided } // Load the configuration file config, err := loadConfig(*configFile) if err != nil { fmt.Printf("Failed to load config: %v\n", err) os.Exit(1) // Exit if the configuration file can't be loaded } // Map to track which tasks have been executed already (avoiding re-execution). executed := make(map[string]bool) // Start executing the specified task (with dependencies) if err := executeTask(*taskName, config.Tasks, executed); err != nil { fmt.Printf("Error executing task: %v\n", err) os.Exit(1) // Exit if task execution fails } }
讓我們測試一下整個過程,建立一個新的 Mommy.yaml 並將開頭的 yaml 程式碼貼到其中。我們將使用任務運行程序為我們的專案建立二進位檔案。運行:
go run main.go -task build
如果一切順利,您將在資料夾的根目錄中看到一個新的 .exe 檔案。太好了,我們現在有了一個可以工作的任務運行程序。我們可以在系統的環境變數中新增此 .exe 檔案的位置,並使用以下命令從任何地方使用它:
mommy -task build
package main import ( "flag" "fmt" "os" "os/exec" "gopkg.in/yaml.v3" ) // Task defines the structure of a task in the configuration file. // Each task has a description, a command to run, and a list of dependencies // (other tasks that need to be completed before this task). type Task struct { Description string `yaml:"description"` // A brief description of the task. Command string `yaml:"command"` // The shell command to execute for the task. Dependencies []string `yaml:"dependencies"` // List of tasks that need to be completed before this task. } // Config represents the entire configuration file, // which contains a map of tasks by name. type Config struct { Tasks map[string]Task `yaml:"tasks"` // A map of task names to task details. } // loadConfig reads and parses the configuration file (e.g., Makefile.yaml), // and returns a Config struct containing the tasks and their details. func loadConfig(filename string) (Config, error) { // Read the content of the config file. data, err := os.ReadFile(filename) if err != nil { return Config{}, err } // Unmarshal the YAML data into a Config struct. var config Config err = yaml.Unmarshal(data, &config) if err != nil { return Config{}, err } return config, nil } // executeTask recursively executes the specified task and its dependencies. // It first ensures that all dependencies are executed before running the current task's command. func executeTask(taskName string, tasks map[string]Task, executed map[string]bool) error { // If the task has already been executed, skip it. if executed[taskName] { return nil } // Get the task details from the tasks map. task, exists := tasks[taskName] if !exists { return fmt.Errorf("task %s not found", taskName) } // First, execute all the dependencies of this task. for _, dep := range task.Dependencies { // Recursively execute each dependency. if err := executeTask(dep, tasks, executed); err != nil { return err } } // Now that dependencies are executed, run the task's command. fmt.Printf("Running task: %s\n", taskName) fmt.Printf("Command: %s\n", task.Command) // Execute the task's command using the shell (sh -c allows for complex shell commands). cmd := exec.Command("sh", "-c", task.Command) cmd.Stdout = os.Stdout // Direct standard output to the terminal. cmd.Stderr = os.Stderr // Direct error output to the terminal. // Run the command and check for any errors. if err := cmd.Run(); err != nil { return fmt.Errorf("failed to execute command %s: %v", task.Command, err) } // Mark the task as executed. executed[taskName] = true return nil } func main() { // Define command-line flags configFile := flag.String("f", "Makefile.yaml", "Path to the configuration file") // Path to the config file (defaults to Makefile.yaml) taskName := flag.String("task", "", "Task to execute") // The task to execute (required flag) // Parse the flags flag.Parse() // Check if the task flag is provided if *taskName == "" { fmt.Println("Error: Please specify a task using -task flag.") os.Exit(1) // Exit if no task is provided } // Load the configuration file config, err := loadConfig(*configFile) if err != nil { fmt.Printf("Failed to load config: %v\n", err) os.Exit(1) // Exit if the configuration file can't be loaded } // Map to track which tasks have been executed already (avoiding re-execution). executed := make(map[string]bool) // Start executing the specified task (with dependencies) if err := executeTask(*taskName, config.Tasks, executed); err != nil { fmt.Printf("Error executing task: %v\n", err) os.Exit(1) // Exit if task execution fails } }
以上是初學者 Go 專案 - 在 Go 中建立任務運行程序的詳細內容。更多資訊請關注PHP中文網其他相關文章!