新增应用打包模块
This commit is contained in:
340
services/pack_service.go
Normal file
340
services/pack_service.go
Normal file
@@ -0,0 +1,340 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-site-clone/utils"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// PackService 打包服务
|
||||
type PackService struct{}
|
||||
|
||||
// PackConfig 打包配置
|
||||
type PackConfig struct {
|
||||
SitePath string // 网站文件夹路径
|
||||
AppName string // 应用名称
|
||||
Version string // 版本号
|
||||
Author string // 作者
|
||||
Description string // 描述
|
||||
Platforms []string // 目标平台
|
||||
Width int // 窗口宽度
|
||||
Height int // 窗口高度
|
||||
OutputDir string // 输出目录
|
||||
}
|
||||
|
||||
// Wails项目模板
|
||||
const mainGoTemplate = `package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"log"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
//go:embed all:frontend
|
||||
var assets embed.FS
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "{{.AppName}}",
|
||||
Description: "{{.Description}}",
|
||||
Assets: application.AssetOptions{
|
||||
Handler: application.AssetFileServerFS(assets),
|
||||
},
|
||||
Mac: application.MacOptions{
|
||||
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||
},
|
||||
})
|
||||
|
||||
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "{{.AppName}}",
|
||||
Mac: application.MacWindow{
|
||||
InvisibleTitleBarHeight: 50,
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
TitleBar: application.MacTitleBarHiddenInset,
|
||||
},
|
||||
BackgroundColour: application.NewRGB(255, 255, 255),
|
||||
URL: "/",
|
||||
Width: {{.Width}},
|
||||
Height: {{.Height}},
|
||||
})
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const goModTemplate = `module {{.ModuleName}}
|
||||
|
||||
go 1.21
|
||||
|
||||
require github.com/wailsapp/wails/v3 v3.0.0-alpha.27
|
||||
`
|
||||
|
||||
// CreateWailsProject 创建Wails项目结构
|
||||
func (p *PackService) CreateWailsProject(config PackConfig, projectDir string) error {
|
||||
// 1. 创建项目目录结构
|
||||
dirs := []string{
|
||||
projectDir,
|
||||
filepath.Join(projectDir, "frontend"),
|
||||
filepath.Join(projectDir, "build"),
|
||||
}
|
||||
for _, dir := range dirs {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("创建目录失败 %s: %v", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 1.1 复制项目根目录的 Taskfile.yml
|
||||
currentDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取当前目录失败: %v", err)
|
||||
}
|
||||
srcTaskfile := filepath.Join(currentDir, "Taskfile.yml")
|
||||
dstTaskfile := filepath.Join(projectDir, "Taskfile.yml")
|
||||
if err := p.copyFile(srcTaskfile, dstTaskfile); err != nil {
|
||||
return fmt.Errorf("复制Taskfile.yml失败: %v", err)
|
||||
}
|
||||
|
||||
// 1.2 复制整个 build 目录
|
||||
srcBuildDir := filepath.Join(currentDir, "build")
|
||||
dstBuildDir := filepath.Join(projectDir, "build")
|
||||
if err := p.copyDirectory(srcBuildDir, dstBuildDir); err != nil {
|
||||
return fmt.Errorf("复制build目录失败: %v", err)
|
||||
}
|
||||
|
||||
// 2. 复制网站文件到frontend目录
|
||||
frontendDir := filepath.Join(projectDir, "frontend")
|
||||
if err := p.copyDirectory(config.SitePath, frontendDir); err != nil {
|
||||
return fmt.Errorf("复制网站文件失败: %v", err)
|
||||
}
|
||||
|
||||
// 2.1 创建一个空的 package.json,避免构建系统报错
|
||||
packageJSON := `{
|
||||
"name": "static-site",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "echo 'Static site, no build needed'",
|
||||
"generate": "echo 'Static site, no build needed'"
|
||||
}
|
||||
}`
|
||||
packageJSONPath := filepath.Join(frontendDir, "package.json")
|
||||
if err := os.WriteFile(packageJSONPath, []byte(packageJSON), 0644); err != nil {
|
||||
return fmt.Errorf("创建package.json失败: %v", err)
|
||||
}
|
||||
|
||||
// 2.2 创建一个空的 dist 目录(如果构建系统需要)
|
||||
distDir := filepath.Join(frontendDir, "dist")
|
||||
if err := os.MkdirAll(distDir, 0755); err != nil {
|
||||
return fmt.Errorf("创建dist目录失败: %v", err)
|
||||
}
|
||||
|
||||
// 3. 生成main.go
|
||||
mainGoPath := filepath.Join(projectDir, "main.go")
|
||||
tmpl, err := template.New("main").Parse(mainGoTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析main.go模板失败: %v", err)
|
||||
}
|
||||
|
||||
mainFile, err := os.Create(mainGoPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建main.go失败: %v", err)
|
||||
}
|
||||
defer mainFile.Close()
|
||||
|
||||
templateData := map[string]interface{}{
|
||||
"AppName": config.AppName,
|
||||
"Description": config.Description,
|
||||
"Width": config.Width,
|
||||
"Height": config.Height,
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(mainFile, templateData); err != nil {
|
||||
return fmt.Errorf("生成main.go失败: %v", err)
|
||||
}
|
||||
|
||||
// 4. 生成go.mod
|
||||
goModPath := filepath.Join(projectDir, "go.mod")
|
||||
modTmpl, err := template.New("gomod").Parse(goModTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析go.mod模板失败: %v", err)
|
||||
}
|
||||
|
||||
modFile, err := os.Create(goModPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建go.mod失败: %v", err)
|
||||
}
|
||||
defer modFile.Close()
|
||||
|
||||
modData := map[string]interface{}{
|
||||
"ModuleName": strings.ToLower(strings.ReplaceAll(config.AppName, " ", "-")),
|
||||
}
|
||||
|
||||
if err := modTmpl.Execute(modFile, modData); err != nil {
|
||||
return fmt.Errorf("生成go.mod失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildWailsApp 构建Wails应用
|
||||
func (p *PackService) BuildWailsApp(projectDir string, outputDir string, platforms []string) error {
|
||||
// 获取环境状态
|
||||
envStatus, err := utils.GetEnvStatus()
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查环境失败: %v", err)
|
||||
}
|
||||
|
||||
// 确定使用的Go命令
|
||||
goCmd := "go"
|
||||
if envStatus.GoPath != "" {
|
||||
goCmd = envStatus.GoPath
|
||||
}
|
||||
|
||||
// 1. 执行 go mod tidy
|
||||
tidyCmd := exec.Command(goCmd, "mod", "tidy")
|
||||
tidyCmd.Dir = projectDir
|
||||
if output, err := tidyCmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("go mod tidy 失败: %v\n%s", err, output)
|
||||
}
|
||||
|
||||
// 2. 直接使用 go build 编译,跳过复杂的构建流程
|
||||
// 创建输出目录
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
return fmt.Errorf("创建输出目录失败: %v", err)
|
||||
}
|
||||
|
||||
// 确定输出文件名
|
||||
appName := filepath.Base(projectDir)
|
||||
outputFile := filepath.Join(outputDir, appName+".exe")
|
||||
|
||||
// 构建命令,使用生产环境标签
|
||||
buildCmd := exec.Command(goCmd, "build",
|
||||
"-tags", "production",
|
||||
"-ldflags", "-w -s -H windowsgui",
|
||||
"-o", outputFile,
|
||||
".")
|
||||
buildCmd.Dir = projectDir
|
||||
|
||||
// 设置环境变量
|
||||
buildCmd.Env = append(os.Environ(),
|
||||
"CGO_ENABLED=1",
|
||||
"GOOS=windows",
|
||||
)
|
||||
|
||||
output, err := buildCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("go build 失败: %v\n%s", err, output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyDirectory 递归复制目录
|
||||
func (p *PackService) copyDirectory(src, dst string) error {
|
||||
// 获取源目录信息
|
||||
srcInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建目标目录
|
||||
if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 读取源目录
|
||||
entries, err := os.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
srcPath := filepath.Join(src, entry.Name())
|
||||
dstPath := filepath.Join(dst, entry.Name())
|
||||
|
||||
if entry.IsDir() {
|
||||
// 递归复制子目录
|
||||
if err := p.copyDirectory(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 复制文件
|
||||
if err := p.copyFile(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyFile 复制单个文件
|
||||
func (p *PackService) copyFile(src, dst string) error {
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
dstFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
if _, err := io.Copy(dstFile, srcFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 复制文件权限
|
||||
srcInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Chmod(dst, srcInfo.Mode())
|
||||
}
|
||||
|
||||
// copyBuildOutput 复制构建输出
|
||||
func (p *PackService) copyBuildOutput(buildDir, outputDir string) error {
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查 buildDir 是否存在
|
||||
if _, err := os.Stat(buildDir); os.IsNotExist(err) {
|
||||
return fmt.Errorf("构建目录不存在: %s", buildDir)
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(buildDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
srcPath := filepath.Join(buildDir, entry.Name())
|
||||
dstPath := filepath.Join(outputDir, entry.Name())
|
||||
|
||||
if entry.IsDir() {
|
||||
if err := p.copyDirectory(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := p.copyFile(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user