Skip to content

Commit

Permalink
Merge pull request #666 from ActiveState/enable-symlinks-172342127
Browse files Browse the repository at this point in the history
Enable linking on other operating systems
  • Loading branch information
MDrakos authored Apr 28, 2020
2 parents 729ba3d + f979477 commit bb6ceca
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 34 deletions.
6 changes: 6 additions & 0 deletions assets/scripts/createShortcut.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
param ( [string]$SourceExe, [string]$DestinationPath )

$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut($DestinationPath)
$Shortcut.TargetPath = $SourceExe
$Shortcut.Save()
12 changes: 5 additions & 7 deletions cmd/state/internal/cmdtree/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@ func newDeployCommand(output output.Outputer) *captain.Command {
},
}

if runtime.GOOS == "linux" {
flags = append(flags, &captain.Flag{
Name: "force",
Description: locale.T("flag_state_deploy_force_description"),
Value: &params.Force,
})
}
flags = append(flags, &captain.Flag{
Name: "force",
Description: locale.T("flag_state_deploy_force_description"),
Value: &params.Force,
})

return captain.NewCommand(
"deploy",
Expand Down
27 changes: 27 additions & 0 deletions internal/fileutils/fileutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/ActiveState/cli/internal/failures"
"github.com/ActiveState/cli/internal/locale"
"github.com/ActiveState/cli/internal/logging"
"github.com/google/uuid"
)

// FailFindInPathNotFound indicates the specified file was not found in the given path or parent directories
Expand Down Expand Up @@ -742,3 +743,29 @@ func HomeDir() (string, error) {

return usr.HomeDir, nil
}

// IsWritable returns true if the given path is writable
func IsWritable(path string) bool {
fpath := filepath.Join(path, uuid.New().String())
if fail := Touch(fpath); fail != nil {
logging.Error("Could not create file: %v", fail.ToError())
return false
}

if errr := os.Remove(fpath); errr != nil {
logging.Error("Could not clean up test file: %v", errr)
return false
}

return true
}

// IsDir returns true if the given path is a directory
func IsDir(path string) bool {
info, err := os.Stat(path)
if err != nil {
logging.Debug("Could not stat path: %s, got error: %v", path, err)
return false
}
return info.IsDir()
}
32 changes: 11 additions & 21 deletions internal/runners/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
rt "runtime"
"strings"

"github.com/google/uuid"
"github.com/thoas/go-funk"

"github.com/ActiveState/cli/internal/errs"
Expand Down Expand Up @@ -90,7 +89,7 @@ func runStepsWithFuncs(targetPath string, force bool, step Step, installer insta
return fail
}

if ! installed && step != UnsetStep && step != InstallStep {
if !installed && step != UnsetStep && step != InstallStep {
return locale.NewInputError("err_deploy_run_install", "Please run the install step at least once")
}

Expand Down Expand Up @@ -119,7 +118,7 @@ func runStepsWithFuncs(targetPath string, force bool, step Step, installer insta
out.Notice("") // Some space between steps
}
}
if rt.GOOS == "linux" && (step == UnsetStep || step == SymlinkStep) {
if step == UnsetStep || step == SymlinkStep {
logging.Debug("Running symlink step")
if envGetter == nil {
if envGetter, fail = installer.Env(); fail != nil {
Expand Down Expand Up @@ -225,8 +224,8 @@ func symlinkWithTarget(overwrite bool, path string, bins []string, out output.Ou

for _, bin := range bins {
err := filepath.Walk(bin, func(fpath string, info os.FileInfo, err error) error {
// Filter out files that are executable
if info == nil || info.IsDir() || info.Mode()&0111 == 0 { // check if executable by anyone
// Filter out files that are not executable
if info == nil || info.IsDir() || !fileutils.IsExecutable(fpath) { // check if executable by anyone
return nil // not executable
}

Expand All @@ -247,14 +246,7 @@ func symlinkWithTarget(overwrite bool, path string, bins []string, out output.Ou
}
}

// Create symlink
err = os.Symlink(fpath, target)
if err != nil {
return locale.WrapInputError(
err, "err_deploy_symlink",
"Cannot create symlink at {{.V0}}, ensure you have permission to write to {{.V1}}.", target, filepath.Dir(target))
}
return nil
return link(fpath, target)
})
if err != nil {
return err
Expand All @@ -276,7 +268,6 @@ func report(envGetter runtime.EnvGetter, out output.Outputer) error {
env := venv.GetEnv(false, "")

var bins []string

if path, ok := env["PATH"]; ok {
delete(env, "PATH")
bins = strings.Split(path, string(os.PathListSeparator))
Expand All @@ -289,7 +280,11 @@ func report(envGetter runtime.EnvGetter, out output.Outputer) error {
Environment: env,
})

out.Notice(locale.T("deploy_restart_shell"))
if rt.GOOS == "windows" {
out.Notice(locale.T("deploy_restart_cmd"))
} else {
out.Notice(locale.T("deploy_restart_shell"))
}

return nil
}
Expand All @@ -307,14 +302,9 @@ func usablePath() (string, error) {
}
var result string
for _, path := range paths {
// Check if we can write to this path
fpath := filepath.Join(path, uuid.New().String())
if err := fileutils.Touch(fpath); err != nil {
if path == "" || !fileutils.IsDir(path) || !fileutils.IsWritable(path) {
continue
}
if errr := os.Remove(fpath); errr != nil {
logging.Error("Could not clean up test file: %v", errr)
}

// Record result
if funk.Contains(preferredPaths, path) {
Expand Down
11 changes: 5 additions & 6 deletions internal/runners/deploy/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package deploy
import (
"os"
"reflect"
rt "runtime"
"testing"

"github.com/ActiveState/cli/internal/failures"
Expand Down Expand Up @@ -61,7 +60,7 @@ func Test_runStepsWithFuncs(t *testing.T) {
nil,
true,
true,
rt.GOOS == "linux",
true,
true,
},
},
Expand Down Expand Up @@ -103,7 +102,7 @@ func Test_runStepsWithFuncs(t *testing.T) {
nil,
false,
false,
rt.GOOS == "linux",
true,
false,
},
},
Expand Down Expand Up @@ -205,16 +204,16 @@ func Test_report(t *testing.T) {
t.FailNow()
}
report, ok := catcher.Prints[0].(Report)
if ! ok {
if !ok {
t.Errorf("Printed unknown structure, expected Report type. Value: %v", report)
t.FailNow()
}

if ! reflect.DeepEqual(report.Environment, tt.wantEnv) {
if !reflect.DeepEqual(report.Environment, tt.wantEnv) {
t.Errorf("Expected envs to be the same. Want: %v, got: %v", tt.wantEnv, report.Environment)
}

if ! reflect.DeepEqual(report.BinaryDirectories, tt.wantBinary) {
if !reflect.DeepEqual(report.BinaryDirectories, tt.wantBinary) {
t.Errorf("Expected bins to be the same. Want: %v, got: %v", tt.wantBinary, report.BinaryDirectories)
}
})
Expand Down
22 changes: 22 additions & 0 deletions internal/runners/deploy/link_lin_mac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// +build !windows

package deploy

import (
"os"
"path/filepath"

"github.com/ActiveState/cli/internal/locale"
"github.com/ActiveState/cli/internal/logging"
)

func link(src, dst string) error {
logging.Debug("Creating symlink, oldname: %s newname: %s", src, dst)
err := os.Symlink(src, dst)
if err != nil {
return locale.WrapInputError(
err, "err_deploy_symlink",
"Cannot create symlink at {{.V0}}, ensure you have permission to write to {{.V1}}.", dst, filepath.Dir(dst))
}
return nil
}
37 changes: 37 additions & 0 deletions internal/runners/deploy/link_win.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// +build windows

package deploy

import (
"os/exec"
"path/filepath"
"strconv"
"strings"

"github.com/ActiveState/cli/internal/environment"
"github.com/ActiveState/cli/internal/locale"
"github.com/ActiveState/cli/internal/logging"
)

func link(src, dst string) error {
if strings.HasSuffix(dst, ".exe") {
dst = strings.Replace(dst, ".exe", ".lnk", 1)
}
logging.Debug("Creating shortcut, oldname: %s newname: %s", src, dst)

root, err := environment.GetRootPath()
if err != nil {
return locale.WrapError(
err, "err_link_get_root",
"Could not get root path of shortcut script",
)
}
scriptPath := filepath.Join(root, "assets", "scripts", "createShortcut.ps1")

// Some paths may contain spaces so we must quote
src = strconv.Quote(src)
dst = strconv.Quote(dst)

cmd := exec.Command("powershell.exe", "-Command", scriptPath, src, dst)
return cmd.Run()
}
4 changes: 4 additions & 0 deletions locale/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1573,6 +1573,10 @@ deploy_restart_shell:
other: |
[BLUE][BOLD]Please restart your terminal or start a new terminal session in order to start using the newly configured Runtime Environment.[/RESET]
deploy_restart_cmd:
other: |
[BLUE][BOLD]Please log out and back in again in order to start using the newly configured Runtime Environment.[/RESET]
flag_state_deploy_path_description:
other: The path to deploy the runtime installation files to. This directory will not be affected by 'state clean'
flag_state_deploy_force_description:
Expand Down

0 comments on commit bb6ceca

Please sign in to comment.