Repository: tensorchord/envd (language: python)

Issue:
bug(CLI): fail to use both path and file flag in envd up
```
envd up --path ./examples/mnist -f ./examples/mnist/build_gpu.envd 
error: no such file or directory
```

We use the path dir as the cwd, thus we cannot find the file `./examples/mnist/build_gpu.envd `

---
<!-- Issue Author: Don't delete this message to encourage other users to support your issue! -->
**Message from the maintainers**:

Impacted by this bug? Give it a 👍. We prioritise the issues with the most 👍.



Discussion / hints:
We can recording pwd as a global 'ENV_DIR' to support both path dir and the current dir.

Is it suitable?
> We can recording pwd as a global 'ENV_DIR' to support both path dir and the current dir.
> 
> Is it suitable?

Could you please explain more about it?
> > We can recording pwd as a global 'ENV_DIR' to support both path dir and the current dir.
> > Is it suitable?
> 
> Could you please explain more about it?

Just record the current working dir by using `pwd()` path as a secondary base search path when it is failed in using the path dir to find the file with`-f`.
Gotcha, SGTM. I think it works.
Is there a bug in https://github.com/tensorchord/envd/blob/53d8e13fc3cf39cd8a1c38e80d411096dd99e234/pkg/builder/util.go#L321-L338

So no matter what you pass to `-f`, it will always use the default`build.envd`. (Unless you specify the target function)

BTW, the `-f` description is "Function to execute, format `file:func`". Should we use `filepath.Base()` to extract the last element?
I think it is a bug.

Fix patch:
diff --git a/pkg/app/build.go b/pkg/app/build.go
index 650a18c7c..fd1526cb8 100644
--- a/pkg/app/build.go
+++ b/pkg/app/build.go
@@ -26,6 +26,7 @@ import (
 	"github.com/tensorchord/envd/pkg/docker"
 	"github.com/tensorchord/envd/pkg/home"
 	sshconfig "github.com/tensorchord/envd/pkg/ssh/config"
+	"github.com/tensorchord/envd/pkg/util/fileutil"
 )
 
 var CommandBuild = &cli.Command{
@@ -149,7 +150,7 @@ func ParseBuildOpt(clicontext *cli.Context) (builder.Options, error) {
 		return builder.Options{}, err
 	}
 
-	manifest, err := filepath.Abs(filepath.Join(buildContext, fileName))
+	manifest, err := fileutil.FindFileAbsPath(buildContext, fileName)
 	if err != nil {
 		return builder.Options{}, errors.Wrap(err, "failed to get absolute path of the build file")
 	}
diff --git a/pkg/app/run.go b/pkg/app/run.go
index 0c58f46cd..52e60f817 100644
--- a/pkg/app/run.go
+++ b/pkg/app/run.go
@@ -25,6 +25,7 @@ import (
 	"github.com/tensorchord/envd/pkg/docker"
 	"github.com/tensorchord/envd/pkg/lang/ir"
 	"github.com/tensorchord/envd/pkg/ssh"
+	"github.com/tensorchord/envd/pkg/util/fileutil"
 )
 
 var CommandRun = &cli.Command{
@@ -84,7 +85,7 @@ func run(clicontext *cli.Context) error {
 		if err != nil {
 			return err
 		}
-		manifest, err := filepath.Abs(filepath.Join(buildContext, fileName))
+		manifest, err := fileutil.FindFileAbsPath(buildContext, fileName)
 		if err != nil {
 			return errors.Wrap(err, "failed to get absolute path of the build file")
 		}
diff --git a/pkg/util/fileutil/file.go b/pkg/util/fileutil/file.go
index a225d9f58..1ece58a2d 100644
--- a/pkg/util/fileutil/file.go
+++ b/pkg/util/fileutil/file.go
@@ -51,6 +51,32 @@ func FileExists(filename string) (bool, error) {
 	return !info.IsDir(), nil
 }
 
+// FindFileAbsPath returns the absolute path for the given path and file
+func FindFileAbsPath(path, fileName string) (string, error) {
+	if len(fileName) <= 0 {
+		return "", errors.New("file name is empty")
+	}
+	manifest := filepath.Join(path, fileName)
+	exist, err := FileExists(manifest)
+	if err != nil {
+		return "", err
+	}
+	var absPath string
+	if exist {
+		absPath, err = filepath.Abs(manifest)
+		if err != nil {
+			return "", err
+		}
+		return absPath, nil
+	}
+	// check if ${PWD}/fileName exists
+	absPath, err = filepath.Abs(fileName)
+	if err != nil {
+		return "", err
+	}
+	return absPath, nil
+}
+
 func RemoveAll(dirname string) error {
 	return os.RemoveAll(dirname)
 }


Test patch:
diff --git a/pkg/util/fileutil/file_test.go b/pkg/util/fileutil/file_test.go
new file mode 100644
index 000000000..72ab5415a
--- /dev/null
+++ b/pkg/util/fileutil/file_test.go
@@ -0,0 +1,82 @@
+// Copyright 2022 The envd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fileutil
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestFindFileAbsPath(t *testing.T) {
+	type testCase struct {
+		name        string
+		filePath    string
+		fileName    string
+		expectPath  string
+		expectError bool
+	}
+
+	dir, err := os.MkdirTemp("", "envd-test")
+	require.Nil(t, err, "create temp dir failed")
+	defer os.RemoveAll(dir)
+	name := "tmpfile"
+	err = os.WriteFile(filepath.Join(dir, name), []byte("test"), 0666)
+	require.Nil(t, err, "write temp file failed")
+	expect, err := filepath.Abs(filepath.Join(dir, name))
+	require.Nil(t, err, "cannot get the abs path")
+
+	testCases := []testCase{
+		{
+			"path + file with path",
+			dir,
+			name,
+			expect,
+			false,
+		},
+		{
+			"empth path + full file",
+			"",
+			expect,
+			expect,
+			false,
+		},
+		{
+			"empty file name",
+			dir,
+			"",
+			"",
+			true,
+		},
+		{
+			"path + full file",
+			dir,
+			expect,
+			expect,
+			false,
+		},
+	}
+
+	for _, tc := range testCases {
+		res, err := FindFileAbsPath(tc.filePath, tc.fileName)
+		if tc.expectError {
+			require.Error(t, err)
+		} else {
+			require.Equal(t, res, tc.expectPath, tc.name)
+		}
+	}
+}