Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli/daemon: support watching embedded files #1667

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions cli/daemon/run/embedded_files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package run

import (
"encr.dev/pkg/watcher"
"fmt"
"os"
"path/filepath"
"strings"
)

var embeddedFiles = make(map[string][]string)

// ignoreEventEmbedded checks whether the event is related to an embedded file
func ignoreEventEmbedded(event watcher.Event) (bool, error) {
switch event.EventType {
case watcher.CREATED:
return true, handleCreatedFile(event.Path)
case watcher.DELETED:
return true, handleDeletedFile(event.Path)
case watcher.MODIFIED:
return handleModifiedFile(event.Path)
default:
return true, nil
}
}

func handleModifiedFile(path string) (bool, error) {
if strings.HasSuffix(path, ".go") {
return true, updateEmbeddedFiles(path)
}

embedded, err := isFileEmbedded(path)
if err != nil {
return true, err
}

return !embedded, nil
}

func initializeEmbeddedFilesTracker(root string) error {
if len(embeddedFiles) > 0 {
return nil
}

return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() || filepath.Ext(path) != ".go" {
return err
}
return updateEmbeddedFiles(path)
})
}

func handleCreatedFile(path string) error {
if strings.HasSuffix(path, ".go") {
return updateEmbeddedFiles(path)
}
return nil
}

func handleDeletedFile(path string) error {
delete(embeddedFiles, path)
return nil
}

func isFileEmbedded(fpath string) (bool, error) {
for _, files := range embeddedFiles {
for _, file := range files {
if file == fpath {
return true, nil
}
}
}
return false, nil
}

func updateEmbeddedFiles(path string) error {
embeds, err := parseEmbeddedFiles(path)
if err != nil {
return fmt.Errorf("failed to parse embedded files: %w", err)
}
embeddedFiles[path] = embeds
return nil
}

// parseEmbeddedFiles returns all the embedded files for a given source file
func parseEmbeddedFiles(sourceFile string) ([]string, error) {
data, err := os.ReadFile(sourceFile)
if err != nil {
return nil, fmt.Errorf("failed to read source file: %w", err)
}

var embeddedPaths []string
sourceDir := filepath.Dir(sourceFile)
lines := strings.Split(string(data), "\n")

for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "//go:embed") {
parts := strings.Fields(line)
if len(parts) > 1 {
dir := parts[1]
filepaths, err := getFilePathsFromDir(filepath.Join(sourceDir, dir))
if err != nil {
return nil, err
}
embeddedPaths = append(embeddedPaths, filepaths...)
}
}
}
return embeddedPaths, nil
}

// getFilePathsFromDir retrieves all file paths from a directory recursively
func getFilePathsFromDir(dir string) ([]string, error) {
var filePaths []string
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return err
}
filePaths = append(filePaths, path)
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to walk through directory %s: %w", dir, err)
}
return filePaths, nil
}
28 changes: 20 additions & 8 deletions cli/daemon/run/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import (
// watch watches the given app for changes, and reports
// them on c.
func (mgr *Manager) watch(run *Run) error {

// Initialize embedded files tracker
if err := initializeEmbeddedFilesTracker(run.App.Root()); err != nil {
return err
}

sub, err := run.App.Watch(func(i *apps.Instance, event []watcher.Event) {
if IgnoreEvents(event) {
return
Expand Down Expand Up @@ -44,23 +50,29 @@ func (mgr *Manager) watch(run *Run) error {
}

// IgnoreEvents will return true if _all_ events are on files that should be ignored
// as the do not impact the running app, or are the result of Encore itself generating code.
// as they do not impact the running app, or are the result of Encore itself generating code.
func IgnoreEvents(events []watcher.Event) bool {
for _, event := range events {
if !ignoreEvent(event) {
filename := filepath.Base(event.Path)
if strings.HasPrefix(strings.ToLower(filename), "encore.gen.") ||
strings.HasSuffix(filename, "~") {
// Ignore generated code and temporary files
return true
}

ignore, err := ignoreEventEmbedded(event)
if err != nil {
return false
}

if !ignoreEvent(event) || !ignore {
return false
}
}
return true
}

func ignoreEvent(ev watcher.Event) bool {
filename := filepath.Base(ev.Path)
if strings.HasPrefix(strings.ToLower(filename), "encore.gen.") {
// Ignore generated code
return true
}

// Ignore files which wouldn't impact the running app
ext := filepath.Ext(ev.Path)
switch ext {
Expand Down