Skip to content

Commit

Permalink
feat(lang): init py env by generating the bulid.envd (#827)
Browse files Browse the repository at this point in the history
* feat(lang): init py env by generating the bulid.envd from existing project

Signed-off-by: Keming <kemingyang@tensorchord.ai>

* add test

Signed-off-by: Keming <kemingyang@tensorchord.ai>

* fix test

Signed-off-by: Keming <kemingyang@tensorchord.ai>

* fix lint

Signed-off-by: Keming <kemingyang@tensorchord.ai>

* only use the first file

Signed-off-by: Keming <kemingyang@tensorchord.ai>

Signed-off-by: Keming <kemingyang@tensorchord.ai>
  • Loading branch information
kemingy committed Aug 31, 2022
1 parent 36b1231 commit 404de31
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 19 deletions.
74 changes: 74 additions & 0 deletions e2e/cli/init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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 cli

import (
"os"
"path/filepath"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/tensorchord/envd/e2e"
"github.com/tensorchord/envd/pkg/app"
"github.com/tensorchord/envd/pkg/home"
"github.com/tensorchord/envd/pkg/util/fileutil"
)

var _ = Describe("init project", Ordered, func() {
var path string
BeforeAll(func() {
Expect(home.Initialize()).To(Succeed())
envdApp := app.New()
err := envdApp.Run([]string{"envd.test", "--debug", "bootstrap"})
Expect(err).To(Succeed())
e2e.ResetEnvdApp()
path, err = os.MkdirTemp("", "envd_init_test_*")
Expect(err).To(Succeed())
err = os.WriteFile(filepath.Join(path, "requirements.txt"), []byte("via"), 0666)
Expect(err).To(Succeed())
})

It("init python env", func() {
envdApp := app.New()
err := envdApp.Run([]string{"envd.test", "--debug", "init", "-p", path})
Expect(err).To(Succeed())
exist, err := fileutil.FileExists(filepath.Join(path, "build.envd"))
Expect(err).To(Succeed())
Expect(exist).To(BeTrue())
})

Describe("run init env", Ordered, func() {
var e *e2e.Example
BeforeAll(func() {
// have to use `path` inside ginkgo closure
e = e2e.NewExample(path, "init_test")
e.RunContainer()()
})
It("exec installed command inside container", func() {
_, err := e.Exec("via --help")
Expect(err).To(Succeed())
})
AfterAll(func() {
e.DestroyContainer()()
e.RemoveImage()()
})

})

AfterAll(func() {
os.RemoveAll(path)
})
})
111 changes: 106 additions & 5 deletions pkg/app/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
package app

import (
"bytes"
"embed"
"fmt"
"io/fs"
"io/ioutil"
"path/filepath"
"strings"

"github.com/cockroachdb/errors"
Expand All @@ -38,14 +42,21 @@ var CommandInit = &cli.Command{
Name: "lang",
Usage: "language usage. Support Python, R, Julia",
Aliases: []string{"l"},
Required: true,
Required: false,
Value: "python",
},
&cli.BoolFlag{
Name: "force",
Usage: "overwrite the build.envd if existed",
Aliases: []string{"f"},
Required: false,
},
&cli.PathFlag{
Name: "path",
Usage: "Path to the directory containing the build.envd",
Aliases: []string{"p"},
Value: ".",
},
},
Action: initCommand,
}
Expand All @@ -61,26 +72,116 @@ func isValidLang(lang string) bool {
return false
}

type pythonEnv struct {
pythonVersion string
requirements string
condaEnv string
indent string
notebook bool
}

func NewPythonEnv(dir string) (*pythonEnv, error) {
requirements := ""
condaEnv := ""
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
relPath, err := filepath.Rel(dir, path)
if err != nil {
return err
}
if d.Name() == "requirements.txt" && len(requirements) <= 0 {
requirements = relPath
return nil
}
if isCondaEnvFile(d.Name()) && len(condaEnv) <= 0 {
condaEnv = relPath
}
return nil
})
if err != nil {
return nil, err
}
return &pythonEnv{
pythonVersion: "python", // use the default one
requirements: requirements,
condaEnv: condaEnv,
indent: " ",
notebook: false,
}, nil
}

func (pe *pythonEnv) generate() []byte {
var buf bytes.Buffer
buf.WriteString("def build():\n")
buf.WriteString(fmt.Sprintf("%sbase(os=\"ubuntu20.04\", language=\"%s\")\n", pe.indent, pe.pythonVersion))
if len(pe.requirements) > 0 {
buf.WriteString(fmt.Sprintf("%sinstall.python_packages(requirements=\"%s\")\n", pe.indent, pe.requirements))
}
if len(pe.condaEnv) > 0 {
buf.WriteString(fmt.Sprintf("%sinstall.conda_packages(env_file=\"%s\")\n", pe.indent, pe.condaEnv))
}
if pe.notebook {
buf.WriteString(fmt.Sprintf("%sconfig.jupyter()\n", pe.indent))
}
return buf.Bytes()
}

// naive check
func isCondaEnvFile(file string) bool {
switch file {
case
"environment.yml",
"environment.yaml",
"env.yml",
"env.yaml":
return true
}
return false
}

func initPythonEnv(dir string) ([]byte, error) {
env, err := NewPythonEnv(dir)
if err != nil {
return nil, err
}
return env.generate(), nil
}

func initCommand(clicontext *cli.Context) error {
lang := strings.ToLower(clicontext.String("lang"))
buildContext, err := filepath.Abs(clicontext.Path("path"))
force := clicontext.Bool("force")
if err != nil {
return err
}
if !isValidLang(lang) {
return errors.Errorf("invalid language %s", lang)
return errors.Errorf("invalid language (%s)", lang)
}

exists, err := fileutil.FileExists("build.envd")
filePath := filepath.Join(buildContext, "build.envd")
exists, err := fileutil.FileExists(filePath)
if err != nil {
return err
}
if exists && !force {
return errors.Errorf("build.envd already exists, use --force to overwrite it")
}

buildEnvdContent, err := templatef.ReadFile("template/" + lang + ".envd")
var buildEnvdContent []byte
if lang == "python" {
buildEnvdContent, err = initPythonEnv(buildContext)
} else {
buildEnvdContent, err = templatef.ReadFile("template/" + lang + ".envd")
}
if err != nil {
return err
}
err = ioutil.WriteFile("build.envd", buildEnvdContent, 0644)
err = ioutil.WriteFile(filePath, buildEnvdContent, 0644)
if err != nil {
return errors.Wrapf(err, "Failed to create build.envd")
}
Expand Down
14 changes: 0 additions & 14 deletions pkg/app/template/python.envd

This file was deleted.

0 comments on commit 404de31

Please sign in to comment.