github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/app/app_darwin.go (about) 1 package app 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "github.com/ActiveState/cli/internal/assets" 10 "github.com/ActiveState/cli/internal/constants" 11 "github.com/ActiveState/cli/internal/errs" 12 "github.com/ActiveState/cli/internal/fileutils" 13 "github.com/ActiveState/cli/internal/logging" 14 "github.com/ActiveState/cli/internal/strutils" 15 ) 16 17 const ( 18 execFileSource = "exec.sh.tpl" 19 launchFileSource = "com.activestate.platform.app.plist.tpl" 20 iconFile = "icon.icns" 21 assetAppDir = "placeholder.app" 22 ) 23 24 func (a *App) install() error { 25 // Create all of the necessary directories and files in a temporary directory 26 // Then move the temporary directory to the final location which for macOS will be the Applications directory 27 tmpDir, err := os.MkdirTemp("", fmt.Sprintf("%s-", a.Name)) 28 if err != nil { 29 return errs.Wrap(err, "Could not create temporary directory") 30 } 31 defer os.RemoveAll(tmpDir) 32 33 appPath := a.Path() 34 35 tmpAppPath := filepath.Join(tmpDir, filepath.Base(appPath)) 36 if err := fileutils.Mkdir(tmpAppPath); err != nil { 37 return errs.Wrap(err, "Could not create .app directory") 38 } 39 40 if err := fileutils.CopyFilesDirReader(assets.NewAssetsFS(), assetAppDir, tmpAppPath, assets.PlaceholderFileName); err != nil { 41 return errs.Wrap(err, "Could not copy files from assets") 42 } 43 44 if err := a.createIcon(tmpAppPath); err != nil { 45 return errs.Wrap(err, "Could not create icon") 46 } 47 48 if err := a.createExecFile(tmpAppPath); err != nil { 49 return errs.Wrap(err, "Could not create exec file") 50 } 51 52 if err := a.createInfoFile(tmpAppPath); err != nil { 53 return errs.Wrap(err, "Could not create info file") 54 } 55 56 installDir := filepath.Dir(appPath) 57 58 if err := fileutils.MkdirUnlessExists(installDir); err != nil { 59 return errs.Wrap(err, "Could not create app parent directory: %s", installDir) 60 } 61 62 if fileutils.DirExists(appPath) { 63 if err := os.RemoveAll(appPath); err != nil { 64 return errs.Wrap(err, "Could not remove existing app directory: %s", appPath) 65 } 66 } 67 68 if err := fileutils.CopyAndRenameFiles(tmpDir, installDir); err != nil { 69 return errs.Wrap(err, "Could not move .app to Applications directory") 70 } 71 72 return nil 73 } 74 75 func (a *App) Path() string { 76 installDir := a.Dir 77 if override := os.Getenv(constants.AppInstallDirOverrideEnvVarName); override != "" { 78 installDir = override 79 } 80 return filepath.Join(installDir, fmt.Sprintf("%s.app", a.Name)) 81 } 82 83 func (a *App) createIcon(path string) error { 84 icon, err := assets.ReadFileBytes(a.options.IconFileSource) 85 if err != nil { 86 return errs.Wrap(err, "Could not read asset") 87 } 88 89 if err = fileutils.WriteFile(filepath.Join(path, "Contents", "Resources", iconFile), icon); err != nil { 90 return errs.Wrap(err, "Could not write icon file") 91 } 92 93 return nil 94 } 95 96 func (a *App) createExecFile(base string) error { 97 path := filepath.Join(base, "Contents", "MacOS") 98 asset, err := assets.ReadFileBytes(execFileSource) 99 if err != nil { 100 return errs.Wrap(err, "Could not read asset") 101 } 102 103 scriptFile := fmt.Sprintf("%s.sh", filepath.Base(a.Exec)) 104 105 content, err := strutils.ParseTemplate( 106 string(asset), 107 map[string]interface{}{ 108 "Exec": a.Exec, 109 "Args": strings.Join(a.Args, " "), 110 }, nil) 111 if err != nil { 112 return errs.Wrap(err, "Could not parse launch file source") 113 } 114 115 err = fileutils.WriteFile(filepath.Join(path, scriptFile), []byte(content)) 116 if err != nil { 117 return errs.Wrap(err, "Could not write Info.plist file") 118 } 119 120 err = os.Chmod(filepath.Join(path, scriptFile), 0755) 121 if err != nil { 122 return errs.Wrap(err, "Could not make executable") 123 } 124 125 return nil 126 } 127 128 func (a *App) createInfoFile(base string) error { 129 path := filepath.Join(base, "Contents") 130 asset, err := assets.ReadFileBytes(launchFileSource) 131 if err != nil { 132 return errs.Wrap(err, "Could not read asset") 133 } 134 135 scriptFile := fmt.Sprintf("%s.sh", filepath.Base(a.Exec)) 136 137 content, err := strutils.ParseTemplate( 138 string(asset), 139 map[string]interface{}{ 140 "Exec": scriptFile, 141 "Icon": a.options.IconFileName, 142 "HideDockIcon": a.options.MacHideDockIcon, 143 "IsGUIApp": a.options.IsGUIApp, 144 }, nil) 145 if err != nil { 146 return errs.Wrap(err, "Could not parse launch file source") 147 } 148 149 err = fileutils.WriteFile(filepath.Join(path, "Info.plist"), []byte(content)) 150 if err != nil { 151 return errs.Wrap(err, "Could not write Info.plist file") 152 } 153 154 return nil 155 } 156 157 func (a *App) uninstall() error { 158 baseDir := a.Dir 159 if override := os.Getenv(constants.AppInstallDirOverrideEnvVarName); override != "" { 160 baseDir = override 161 } 162 163 installDir := filepath.Join(baseDir, fmt.Sprintf("%s.app", a.Name)) 164 if !fileutils.DirExists(installDir) { 165 logging.Debug("Directory does not exist, nothing to do") 166 return nil 167 } 168 169 if err := os.RemoveAll(installDir); err != nil { 170 logging.Debug("Could not remove %s: %v", installDir, err) 171 return errs.Wrap(err, "Could not remove .app from Applications directory") 172 } 173 174 return nil 175 }