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  }