Skip to content

Commit

Permalink
Experimental way to expose tasks, adding them to global scope.
Browse files Browse the repository at this point in the history
  • Loading branch information
bilus committed Oct 23, 2021
1 parent 38d79d1 commit 998a6e6
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 15 deletions.
70 changes: 70 additions & 0 deletions features/expose.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
Feature: Exposing imported package tasks so they can be invoked without the alias.

Background:
Given I'm in project dir


Scenario: Expose tasks
Given file ./Oyafile containing
"""
Project: main
"""
And file ./project1/Oyafile containing
"""
Values:
foo: project1
echo: |
echo "project1"
"""
And file ./project2/Oyafile containing
"""
Import:
p: /project1
Expose: p
"""
And I'm in the ./project2 dir
When I run "oya run echo"
Then the command succeeds
And the command outputs
"""
project1
"""

@current
Scenario: Never overwrite existing an task when exposing tasks
Given file ./Oyafile containing
"""
Project: main
"""
And file ./project1/Oyafile containing
"""
Values:
foo: project1
echo: |
echo "project1"
"""
And file ./project2/Oyafile containing
"""
Import:
p: /project1
Expose: p
echo: |
echo "project2"
"""
And I'm in the ./project2 dir
When I run "oya run echo"
Then the command succeeds
And the command outputs
"""
project2
"""


# TODO: Show as aliases when listing tasks.
11 changes: 10 additions & 1 deletion pkg/oyafile/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ func (oyafile *Oyafile) resolveImports(loader PackLoader) error {
if err != nil {
return err
}

// TODO(bilus): Extract function.
err = oyafile.Values.UpdateScopeAt(string(alias),
func(scope template.Scope) template.Scope {
// Values in the main Oyafile overwrite values in the pack Oyafile.
merged := packOyafile.Values.Merge(scope)
// Task is keeping a pointed to the scope.
// Task is keeping a pointer to the scope.
packOyafile.Values.Replace(merged)
return merged
}, false)
Expand All @@ -51,6 +53,13 @@ func (oyafile *Oyafile) resolveImports(loader PackLoader) error {
oyafile.Tasks.ImportTasks(alias, packOyafile.Tasks)
}

return oyafile.expose()
}

func (oyafile *Oyafile) expose() error {
for _, alias := range oyafile.ExposedAliases {
oyafile.Tasks.Expose(alias)
}
return nil
}

Expand Down
23 changes: 13 additions & 10 deletions pkg/oyafile/oyafile.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,19 @@ type PackReference struct {
type PackReplacements map[types.ImportPath]string

type Oyafile struct {
Dir string
Path string
RootDir string
Shell string
Imports map[types.Alias]types.ImportPath
Tasks task.Table
Values template.Scope
Project string // Project is set for root Oyafile.
Ignore []string // Ignore contains directory exclusion rules.
Requires []PackReference
Dir string
Path string
RootDir string
Shell string
Imports map[types.Alias]types.ImportPath
Tasks task.Table
// ExposedAliases contains a list of imported packs' aliases that will
// be exposed to global scope.
ExposedAliases []types.Alias
Values template.Scope
Project string // Project is set for root Oyafile.
Ignore []string // Ignore contains directory exclusion rules.
Requires []PackReference
// Replacements map packs to local paths relative to project root directory for development based on the Replace: directive.
Replacements PackReplacements
IsBuilt bool
Expand Down
13 changes: 13 additions & 0 deletions pkg/oyafile/parsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
)

const StmtImport = "Import"
const StmtExpose = "Expose"
const StmtValues = "Values"
const StmtProject = "Project"
const StmtIgnore = "Ignore"
Expand Down Expand Up @@ -80,6 +81,8 @@ func parseStatement(name string, value interface{}, o *Oyafile) error {
return parseRequire(value, o)
case StmtReplace:
return parseReplace(value, o)
case StmtExpose:
return parseExpose(value, o)

default:
taskName := task.Name(name)
Expand Down Expand Up @@ -244,3 +247,13 @@ func parseReplace(value interface{}, o *Oyafile) error {
return nil

}

func parseExpose(value interface{}, o *Oyafile) error {
aliasStr, ok := value.(string)
if !ok {
return fmt.Errorf("expected an import alias")
}
alias := types.Alias(aliasStr)
o.ExposedAliases = []types.Alias{alias}
return nil
}
13 changes: 13 additions & 0 deletions pkg/task/mocks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package task

import (
"io"

"github.com/tooploox/oya/pkg/template"
)

type MockTask struct{}

func (MockTask) Exec(workDir string, args []string, scope template.Scope, stdout, stderr io.Writer) error {
return nil
}
11 changes: 11 additions & 0 deletions pkg/task/name.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ func (n Name) Split() (types.Alias, string) {
}
}

func (n Name) IsAliased(alias types.Alias) bool {
// TODO(bilus): Turn Name into a a struct.
actualAlias, _ := n.Split()
return actualAlias == alias
}

func (n Name) Unaliased() Name {
_, s := n.Split()
return Name(s)
}

func (n Name) IsBuiltIn() bool {
firstChar := string(n)[0:1]
return firstChar == strings.ToUpper(firstChar)
Expand Down
23 changes: 23 additions & 0 deletions pkg/task/name_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,26 @@ func TestName_Split_WithNestedAlias(t *testing.T) {
tu.AssertEqual(t, types.Alias("foo.bar"), alias)
tu.AssertEqual(t, "zoo", task)
}

func TestName_IsAliased(t *testing.T) {
testCases := []struct {
name task.Name
alias types.Alias
isAliased bool
}{
{task.Name("name"), types.Alias("alias"), false},
{task.Name("name").Aliased("alias"), types.Alias("alias"), true},
{task.Name("name").Aliased("otherAlias"), types.Alias("alias"), false},
}

for _, tc := range testCases {
tu.AssertEqual(t, tc.isAliased, tc.name.IsAliased(tc.alias))
}
}

func TestName_Unaliased(t *testing.T) {
globalName := task.Name("name")
aliasedName := globalName.Aliased(types.Alias("alias"))
tu.AssertEqual(t, globalName, aliasedName.Unaliased())
tu.AssertEqual(t, globalName, globalName.Unaliased())
}
21 changes: 21 additions & 0 deletions pkg/task/reference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package task

import (
"io"

"github.com/tooploox/oya/pkg/template"
)

type reference struct {
task Task
}

func NewReference(task Task) Task {
return reference{
task: task,
}
}

func (r reference) Exec(workDir string, args []string, scope template.Scope, stdout, stderr io.Writer) error {
return r.task.Exec(workDir, args, scope, stdout, stderr)
}
25 changes: 21 additions & 4 deletions pkg/task/table.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package task

import (
"fmt"

"github.com/tooploox/oya/pkg/types"
)

Expand Down Expand Up @@ -34,12 +32,31 @@ func (tt Table) AddDoc(taskName Name, s string) {
}

func (tt Table) ImportTasks(alias types.Alias, other Table) {
for key, t := range other.tasks {
for name, t := range other.tasks {
// TODO: Detect if task already set.
tt.AddTask(Name(fmt.Sprintf("%v.%v", alias, key)), t)
tt.AddTask(Name(name).Aliased(alias), t)
}
}

// Expose copies tasks under an alias to global scope (without the alias)
// never overriding the existing global tasks.
func (tt Table) Expose(alias types.Alias) {
for name, task := range tt.tasks {
if name.IsAliased(alias) {
globalName := name.Unaliased()
_, ok := tt.LookupTask(globalName)
if !ok {
tt.addTaskWithMeta(globalName, task, tt.meta[name])
}
}
}
}

func (tt Table) addTaskWithMeta(name Name, task Task, meta Meta) {
tt.tasks[name] = task
tt.meta[name] = meta
}

func (tt Table) ForEach(f func(taskName Name, task Task, meta Meta) error) error {
for taskName, task := range tt.tasks {
meta := tt.meta[taskName]
Expand Down
24 changes: 24 additions & 0 deletions pkg/task/table_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package task_test

import (
"testing"

"github.com/tooploox/oya/pkg/task"
"github.com/tooploox/oya/pkg/types"
tu "github.com/tooploox/oya/testutil"
)

func TestTable_Expose(t *testing.T) {
tt := task.NewTable()

globalName := task.Name("task")
aliasedName := globalName.Aliased("alias")
tt.AddTask(aliasedName, task.MockTask{})

tt.Expose(types.Alias("alias"))

_, ok := tt.LookupTask(aliasedName)
tu.AssertTrue(t, ok, "Aliased task not found")
_, ok = tt.LookupTask(globalName)
tu.AssertTrue(t, ok, "Global task not found")
}
5 changes: 5 additions & 0 deletions todo.org
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
- State "CANCELLED" from "TODO" [2019-01-25 Fri 10:34] \\
It's enough to have "install" task by convention in packs and then oya tasks will show it.
:END:
* TODO Experiment: exposing tasks
** TODO PoC
** TODO Flag for go import
** TODO Multiple packages?
** TODO Transient exposure (package exposing another package)
* TODO Vendoring is only partially implemented
** TODO Simplify oya get/vendor (based on Import statements) TBD
**** Just use Import
Expand Down

0 comments on commit 998a6e6

Please sign in to comment.