github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/tiltfile/os/os.go (about)

     1  package os
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"runtime"
     8  
     9  	"go.starlark.net/starlark"
    10  
    11  	"github.com/tilt-dev/tilt/internal/tiltfile/starkit"
    12  	"github.com/tilt-dev/tilt/internal/tiltfile/value"
    13  )
    14  
    15  // The starlark OS module.
    16  // Modeled after Bazel's repository_os
    17  // https://docs.bazel.build/versions/master/skylark/lib/repository_os.html
    18  // and Python's OS module
    19  // https://docs.python.org/3/library/os.html
    20  type Plugin struct {
    21  }
    22  
    23  func NewPlugin() Plugin {
    24  	return Plugin{}
    25  }
    26  
    27  func (e Plugin) OnStart(env *starkit.Environment) error {
    28  	err := env.AddBuiltin("os.getcwd", cwd)
    29  	if err != nil {
    30  		return err
    31  	}
    32  
    33  	err = addPathBuiltin(env, "os.path.abspath", abspath)
    34  	if err != nil {
    35  		return err
    36  	}
    37  	err = env.AddBuiltin("os.path.relpath", relpath)
    38  	if err != nil {
    39  		return err
    40  	}
    41  	err = addPathBuiltin(env, "os.path.basename", basename)
    42  	if err != nil {
    43  		return err
    44  	}
    45  	err = addPathBuiltin(env, "os.path.dirname", dirname)
    46  	if err != nil {
    47  		return err
    48  	}
    49  	err = addPathBuiltin(env, "os.path.exists", exists)
    50  	if err != nil {
    51  		return err
    52  	}
    53  	err = env.AddBuiltin("os.path.join", join)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	err = addPathBuiltin(env, "os.path.realpath", realpath)
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	err = env.AddValue("os.environ", Environ{})
    63  	if err != nil {
    64  		return err
    65  	}
    66  	err = env.AddBuiltin("os.getenv", getenv)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	err = env.AddBuiltin("os.putenv", putenv)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	err = env.AddBuiltin("os.unsetenv", unsetenv)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	return env.AddValue("os.name", starlark.String(osName()))
    80  }
    81  
    82  // For consistency with
    83  // https://docs.python.org/3/library/os.html#os.name
    84  func osName() string {
    85  	if runtime.GOOS == "windows" {
    86  		return "nt"
    87  	}
    88  	return "posix"
    89  }
    90  
    91  func getenv(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
    92  	var key value.Stringable
    93  	var defaultVal starlark.Value = starlark.None
    94  	err := starkit.UnpackArgs(t, fn.Name(), args, kwargs,
    95  		"key", &key,
    96  		"default?", &defaultVal,
    97  	)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	envVal, found := os.LookupEnv(key.Value)
   103  	if !found {
   104  		return defaultVal, nil
   105  	}
   106  
   107  	return starlark.String(envVal), nil
   108  }
   109  
   110  func putenv(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   111  	var key, v value.Stringable
   112  	err := starkit.UnpackArgs(t, fn.Name(), args, kwargs,
   113  		"key", &key,
   114  		"value", &v,
   115  	)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	os.Setenv(key.Value, v.Value)
   121  	return starlark.None, nil
   122  }
   123  
   124  func unsetenv(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   125  	var key value.Stringable
   126  	err := starkit.UnpackArgs(t, fn.Name(), args, kwargs,
   127  		"key", &key,
   128  	)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	os.Unsetenv(key.Value)
   134  	return starlark.None, nil
   135  }
   136  
   137  // Fetch the working directory of current Tiltfile execution.
   138  // All built-ins will be executed relative to this directory (e.g., local(), docker_build(), etc)
   139  // Intended to mirror the API of Python's getcwd
   140  // https://docs.python.org/3/library/os.html#os.getcwd
   141  func cwd(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   142  	err := starkit.UnpackArgs(t, fn.Name(), args, kwargs)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	dir := starkit.AbsWorkingDir(t)
   148  	return starlark.String(dir), nil
   149  }
   150  
   151  // Add a function that takes exactly one parameter, a path string.
   152  func addPathBuiltin(env *starkit.Environment, name string,
   153  	f func(t *starlark.Thread, s string) (starlark.Value, error)) error {
   154  	builtin := func(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   155  		var path string
   156  		err := starkit.UnpackArgs(t, fn.Name(), args, kwargs,
   157  			"path", &path,
   158  		)
   159  		if err != nil {
   160  			return nil, err
   161  		}
   162  		return f(t, path)
   163  	}
   164  	return env.AddBuiltin(name, starkit.Function(builtin))
   165  }
   166  
   167  func abspath(t *starlark.Thread, path string) (starlark.Value, error) {
   168  	return starlark.String(starkit.AbsPath(t, path)), nil
   169  }
   170  
   171  func relpath(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   172  	var targpath string
   173  	basepath := starkit.AbsWorkingDir(thread) // by default, relative to CWD
   174  
   175  	// Matches syntax of Python's os.path.relpath()
   176  	if err := starkit.UnpackArgs(thread, fn.Name(), args, kwargs,
   177  		"targpath", &targpath,
   178  		"basepath?", &basepath); err != nil {
   179  		return nil, err
   180  	}
   181  	relPath, err := filepath.Rel(basepath, targpath)
   182  	return starlark.String(relPath), err
   183  }
   184  
   185  func basename(t *starlark.Thread, path string) (starlark.Value, error) {
   186  	return starlark.String(filepath.Base(path)), nil
   187  }
   188  
   189  func dirname(t *starlark.Thread, path string) (starlark.Value, error) {
   190  	return starlark.String(filepath.Dir(path)), nil
   191  }
   192  
   193  func exists(t *starlark.Thread, path string) (starlark.Value, error) {
   194  	absPath := starkit.AbsPath(t, path)
   195  
   196  	_, err := os.Stat(absPath)
   197  	if err != nil {
   198  		// Return false on error (either not found errors or permission denied
   199  		// errors), for consistency with the python version.
   200  		return starlark.Bool(false), nil
   201  	}
   202  
   203  	return starlark.Bool(true), nil
   204  }
   205  
   206  func join(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   207  	parts := []string{}
   208  	for i, arg := range args {
   209  		s, ok := starlark.AsString(arg)
   210  		if !ok {
   211  			return nil, fmt.Errorf("os.path.join() only accepts strings. Argument #%d: %s", i, arg)
   212  		}
   213  		parts = append(parts, s)
   214  	}
   215  	return starlark.String(filepath.Join(parts...)), nil
   216  }
   217  
   218  func realpath(t *starlark.Thread, path string) (starlark.Value, error) {
   219  	realPath, err := filepath.EvalSymlinks(starkit.AbsPath(t, path))
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  	return starlark.String(realPath), nil
   224  }