Skip to content

Commit 7ddb08b

Browse files
authored
Merge pull request #133 from cashapp/juho/runtime-deps
Runtime Dependencies
2 parents 8ff1db1 + e50bd42 commit 7ddb08b

File tree

15 files changed

+170
-31
lines changed

15 files changed

+170
-31
lines changed

docs/content/packaging/schema/channel.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Used by: [<manifest>](../manifest#blocks)
3232
| `rename` | `{string: string}?` | Rename files after unpacking to ${root}. |
3333
| `requires` | `[string]?` | Packages this one requires. |
3434
| `root` | `string?` | Override root for package. |
35+
| `runtime-dependencies` | `[string]?` | Packages used internally by this package, but not installed to the target environment |
3536
| `sha256` | `string?` | SHA256 of source package for verification. |
3637
| `source` | `string?` | URL for source package. Valid URLs are Git repositories (using .git suffix), Local Files (using file:// prefix), and Remote Files (using http:// or https:// prefix) |
3738
| `strip` | `number?` | Number of path prefix elements to strip. |

docs/content/packaging/schema/darwin.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Used by: [channel](../channel#blocks) [linux](../linux#blocks) [<manifest>](.
3232
| `rename` | `{string: string}?` | Rename files after unpacking to ${root}. |
3333
| `requires` | `[string]?` | Packages this one requires. |
3434
| `root` | `string?` | Override root for package. |
35+
| `runtime-dependencies` | `[string]?` | Packages used internally by this package, but not installed to the target environment |
3536
| `sha256` | `string?` | SHA256 of source package for verification. |
3637
| `source` | `string?` | URL for source package. Valid URLs are Git repositories (using .git suffix), Local Files (using file:// prefix), and Remote Files (using http:// or https:// prefix) |
3738
| `strip` | `number?` | Number of path prefix elements to strip. |

docs/content/packaging/schema/linux.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Used by: [channel](../channel#blocks) [darwin](../darwin#blocks) [<manifest>]
3232
| `rename` | `{string: string}?` | Rename files after unpacking to ${root}. |
3333
| `requires` | `[string]?` | Packages this one requires. |
3434
| `root` | `string?` | Override root for package. |
35+
| `runtime-dependencies` | `[string]?` | Packages used internally by this package, but not installed to the target environment |
3536
| `sha256` | `string?` | SHA256 of source package for verification. |
3637
| `source` | `string?` | URL for source package. Valid URLs are Git repositories (using .git suffix), Local Files (using file:// prefix), and Remote Files (using http:// or https:// prefix) |
3738
| `strip` | `number?` | Number of path prefix elements to strip. |

docs/content/packaging/schema/manifest.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Each Hermit package manifest is a nested structure containing OS/architecture-sp
3434
| `rename` | `{string: string}?` | Rename files after unpacking to ${root}. |
3535
| `requires` | `[string]?` | Packages this one requires. |
3636
| `root` | `string?` | Override root for package. |
37+
| `runtime-dependencies` | `[string]?` | Packages used internally by this package, but not installed to the target environment |
3738
| `sha256` | `string?` | SHA256 of source package for verification. |
3839
| `source` | `string?` | URL for source package. Valid URLs are Git repositories (using .git suffix), Local Files (using file:// prefix), and Remote Files (using http:// or https:// prefix) |
3940
| `strip` | `number?` | Number of path prefix elements to strip. |

docs/content/packaging/schema/platform.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Used by: [channel](../channel#blocks) [darwin](../darwin#blocks) [linux](../linu
3232
| `rename` | `{string: string}?` | Rename files after unpacking to ${root}. |
3333
| `requires` | `[string]?` | Packages this one requires. |
3434
| `root` | `string?` | Override root for package. |
35+
| `runtime-dependencies` | `[string]?` | Packages used internally by this package, but not installed to the target environment |
3536
| `sha256` | `string?` | SHA256 of source package for verification. |
3637
| `source` | `string?` | URL for source package. Valid URLs are Git repositories (using .git suffix), Local Files (using file:// prefix), and Remote Files (using http:// or https:// prefix) |
3738
| `strip` | `number?` | Number of path prefix elements to strip. |

docs/content/packaging/schema/version.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Used by: [<manifest>](../manifest#blocks)
3333
| `rename` | `{string: string}?` | Rename files after unpacking to ${root}. |
3434
| `requires` | `[string]?` | Packages this one requires. |
3535
| `root` | `string?` | Override root for package. |
36+
| `runtime-dependencies` | `[string]?` | Packages used internally by this package, but not installed to the target environment |
3637
| `sha256` | `string?` | SHA256 of source package for verification. |
3738
| `source` | `string?` | URL for source package. Valid URLs are Git repositories (using .git suffix), Local Files (using file:// prefix), and Remote Files (using http:// or https:// prefix) |
3839
| `strip` | `number?` | Number of path prefix elements to strip. |

env.go

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,11 @@ func (e *Env) Test(l *ui.UI, pkg *manifest.Package) error {
394394
return errors.Errorf("couldn't find test executable %q in package %s", args[0], pkg)
395395
}
396396
cmd, _ := util.Command(task, args...)
397-
cmd.Env = e.allEnvarsForPackages(true, pkg)
397+
deps, err := e.ensureRuntimeDepsPresent(l, pkg)
398+
if err != nil {
399+
return errors.WithStack(err)
400+
}
401+
cmd.Env = e.allEnvarsForPackages(true, deps, pkg)
398402
return cmd.Run()
399403
}
400404

@@ -441,27 +445,83 @@ func (e *Env) Install(l *ui.UI, pkg *manifest.Package) (*shell.Changes, error) {
441445
}
442446
}
443447

444-
changes, err := e.install(task, pkg)
448+
changes, err := e.install(l, pkg)
445449
if err != nil {
446450
return nil, errors.WithStack(err)
447451
}
448452

449453
return allChanges.Merge(changes), nil
450454
}
451455

456+
// resolveRuntimeDependencies checks all runtime dependencies for a package are available.
457+
// aggregate and bin collect the package names and binaries of all runtime dependencies to avoid collisions.
458+
func (e *Env) resolveRuntimeDependencies(l *ui.UI, p *manifest.Package, aggregate map[string]*manifest.Package, bins map[string]bool) error {
459+
for _, ref := range p.RuntimeDeps {
460+
previous := aggregate[ref.Name]
461+
if previous != nil && previous.Reference.Compare(ref) != 0 {
462+
return errors.Errorf("two conflicting runtime-dependencies: %s vs %s", ref, previous.Reference)
463+
}
464+
465+
depPkg, err := e.Resolve(l, manifest.ExactSelector(ref), true)
466+
if err != nil {
467+
return errors.WithStack(err)
468+
}
469+
470+
for _, bin := range depPkg.Binaries {
471+
base := filepath.Base(bin)
472+
if bins[base] {
473+
return errors.Errorf("conflicting binary in multiple runtime dependencies: %s", base)
474+
}
475+
bins[base] = true
476+
}
477+
478+
aggregate[depPkg.Reference.Name] = depPkg
479+
if err := e.resolveRuntimeDependencies(l, depPkg, aggregate, bins); err != nil {
480+
return err
481+
}
482+
}
483+
484+
return nil
485+
}
486+
487+
func (e *Env) ensureRuntimeDepsPresent(l *ui.UI, p *manifest.Package) ([]*manifest.Package, error) {
488+
deps := map[string]*manifest.Package{}
489+
err := e.resolveRuntimeDependencies(l, p, deps, map[string]bool{})
490+
if err != nil {
491+
return nil, errors.WithStack(err)
492+
}
493+
result := make([]*manifest.Package, 0, len(deps))
494+
for _, pkg := range deps {
495+
if err := e.state.CacheAndUnpack(l.Task(p.Reference.String()), pkg); err != nil {
496+
return nil, errors.WithStack(err)
497+
}
498+
// Update usage for runtime dependencies so they wont get GC'd
499+
if err := e.state.WritePackageState(pkg, e.binDir); err != nil {
500+
return nil, errors.WithStack(err)
501+
}
502+
result = append(result, pkg)
503+
}
504+
return result, nil
505+
}
506+
452507
// install a package
453-
func (e *Env) install(l *ui.Task, p *manifest.Package) (*shell.Changes, error) {
508+
func (e *Env) install(l *ui.UI, p *manifest.Package) (*shell.Changes, error) {
509+
task := l.Task(p.Reference.String())
510+
511+
if _, err := e.ensureRuntimeDepsPresent(l, p); err != nil {
512+
return nil, errors.WithStack(err)
513+
}
454514
p.UpdatedAt = time.Now()
455-
log := l.SubTask("install")
515+
log := task.SubTask("install")
456516
log.Infof("Installing %s", p)
457517
log.Debugf("From %s", p.Source)
458518
log.Debugf("To %s", p.Dest)
459-
err := e.state.CacheAndUnpack(l, p)
519+
err := e.state.CacheAndUnpack(task, p)
460520
if err != nil {
461521
return nil, errors.WithStack(err)
462522
}
463523
if _, err := os.Stat(e.pkgLink(p)); os.IsNotExist(err) {
464-
if err = e.linkPackage(l, p); err != nil {
524+
if err = e.linkPackage(task, p); err != nil {
465525
return nil, errors.WithStack(err)
466526
}
467527
if err = e.state.WritePackageState(p, e.binDir); err != nil {
@@ -544,15 +604,24 @@ func (e *Env) Exec(l *ui.UI, pkg *manifest.Package, binary string, args []string
544604
if err != nil {
545605
return errors.WithStack(err)
546606
}
547-
err = e.EnsureChannelIsUpToDate(l, pkg)
607+
if err := e.EnsureChannelIsUpToDate(l, pkg); err != nil {
608+
return errors.WithStack(err)
609+
}
610+
runtimeDeps, err := e.ensureRuntimeDepsPresent(l, pkg)
548611
if err != nil {
549612
return errors.WithStack(err)
550613
}
551614
binaries, err := pkg.ResolveBinaries()
552615
if err != nil {
553616
return errors.WithStack(err)
554617
}
555-
env, err := e.Envars(l, true)
618+
619+
installed, err := e.ListInstalled(l)
620+
if err != nil {
621+
return errors.WithStack(err)
622+
}
623+
env := e.allEnvarsForPackages(true, runtimeDeps, installed...)
624+
556625
if err != nil {
557626
return errors.WithStack(err)
558627
}
@@ -740,7 +809,7 @@ func (e *Env) Envars(l *ui.UI, inherit bool) ([]string, error) {
740809
if err != nil {
741810
return nil, err
742811
}
743-
return e.allEnvarsForPackages(inherit, pkgs...), nil
812+
return e.allEnvarsForPackages(inherit, nil, pkgs...), nil
744813
}
745814

746815
// EnvOps returns the envar mutation operations for this environment.
@@ -890,7 +959,7 @@ func (e *Env) upgradeVersion(l *ui.UI, pkg *manifest.Package) (*shell.Changes, e
890959
if err != nil {
891960
return nil, errors.WithStack(err)
892961
}
893-
ic, err := e.install(l.Task(resolved.Reference.String()), resolved)
962+
ic, err := e.install(l, resolved)
894963
if err != nil {
895964
return nil, errors.WithStack(err)
896965
}
@@ -985,9 +1054,10 @@ func (e *Env) pkgLink(pkg *manifest.Package) string {
9851054
// Returns combined system + Hermit + package environment variables, fully expanded.
9861055
//
9871056
// If "inherit" is true, system envars will be included.
988-
func (e *Env) allEnvarsForPackages(inherit bool, pkgs ...*manifest.Package) []string {
1057+
func (e *Env) allEnvarsForPackages(inherit bool, runtimeDeps []*manifest.Package, pkgs ...*manifest.Package) []string {
9891058
var ops envars.Ops
9901059
system := envars.Parse(os.Environ())
1060+
ops = append(ops, e.hermitRuntimeDepOps(runtimeDeps)...)
9911061
ops = append(ops, e.envarsForPackages(pkgs...)...)
9921062
ops = append(ops, e.localEnvarOps()...)
9931063
ops = append(ops, e.hermitEnvarOps()...)
@@ -1013,7 +1083,7 @@ func (e *Env) localEnvarOps() envars.Ops {
10131083
return envars.Infer(e.config.Envars.System())
10141084
}
10151085

1016-
// hermitEnvarOps returns the environment variables created and reuqired by hermit itself
1086+
// hermitEnvarOps returns the environment variables created and required by hermit itself
10171087
func (e *Env) hermitEnvarOps() envars.Ops {
10181088
return envars.Ops{
10191089
&envars.Prepend{Name: "PATH", Value: e.binDir},
@@ -1022,6 +1092,15 @@ func (e *Env) hermitEnvarOps() envars.Ops {
10221092
}
10231093
}
10241094

1095+
// hermitRuntimeDepOps returns the environment variables for runtime dependencies
1096+
func (e *Env) hermitRuntimeDepOps(pkgs []*manifest.Package) envars.Ops {
1097+
ops := e.envarsForPackages(pkgs...)
1098+
for _, pkg := range pkgs {
1099+
ops = append(ops, &envars.Prepend{Name: "PATH", Value: filepath.Join(e.state.BinaryDir(), pkg.Reference.String())})
1100+
}
1101+
return ops
1102+
}
1103+
10251104
func (e *Env) linkApp(app string) error {
10261105
root := filepath.Join(e.binDir, filepath.Base(app))
10271106
for _, dir := range []string{"Contents/MacOS", "Contents/Resources"} {

it/full/run.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ setupEnv() {
3434

3535
ESCAPED_PWD=$(printf '%s\n' "$PWD" | sed -e 's/[\/&]/\\&/g')
3636
sed -i.bak "s/#PWD/${ESCAPED_PWD}/g" "${to}/bin/hermit.hcl"
37+
38+
(
39+
cd ../testbins
40+
tar cvzf testbin1.tar.gz testbin1
41+
tar cvzf testbin2.tar.gz testbin2
42+
)
43+
mv ../testbins/*.tar.gz "${to}/bin/"
3744
}
3845

3946
beforeEach() {

it/full/spec/it_spec.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,22 @@ Describe "Hermit"
189189
The stderr should not be blank
190190
End
191191
End
192+
193+
Describe "Runtime dependencies"
194+
. bin/activate-hermit
195+
It "Does not install runtime-dependencies to the environment"
196+
When call hermit install testbin1
197+
The status should be success
198+
The stderr should be blank
199+
The file ./bin/testbin1 should be exist
200+
The file ./bin/testbin2 should not be exist
201+
End
202+
203+
It "Calls the runtime dependency correctly"
204+
When call ./bin/testbin1
205+
The status should be success
206+
The stdout should equal "Hello from testbin2"
207+
The stderr should be blank
208+
End
209+
End
192210
End

it/packages/testbin1.hcl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
description = "test package"
2+
binaries = ["testbin1"]
3+
4+
source = "${env}/bin/testbin1.tar.gz"
5+
6+
runtime-dependencies=["testbin2-1.0.0"]
7+
8+
version "1.0.0" {}

0 commit comments

Comments
 (0)