#@Erik Sipsma I can't seem to make

1 messages ยท Page 1 of 1 (latest)

spiral hamlet
#

Ah okay, you're hitting a slight variation on https://github.com/dagger/dagger/issues/6366, I'll update the wording of that issue to account for fixing this too though.

Basically, right now to get the .As* methods the interface needs to be in a dep, the engine doesn't yet take into account interfaces your module defined and apply them to your deps.

So the idea right now is that archivist (or some other dep) would be the one defining an Archiver interface and then it would have some function in there that either accepts an Archiver as an arg or returns one.

But the thing is, I'm not sure whether you actually need interfaces here yet? At least in the current set of functionality. You can just skip the .AsArchiver() in the caller package and it should work as expected.

Hypothetical situations where I could imagine interfaces actually being needed would be more like if the implementations of all the different archive types were spread across different modules.

Or maybe if you wanted to implement arbitrary conversions between different archive types you could do that via something like this in the archivist module:

// represents a compressed file
type Archive interface {
  DaggerObject
  File() *File
  Dir() *Directory
  CompressionType() string
}

// Archiver archives a directory of files and returns a single archive.
type Archiver interface {
    DaggerObject
    Archive(ctx context.Context, name string, source *Directory) Archive
}

// Convert archive file formats
func (m *Archivist) Convert(ctx context.Context, sourceArchive Archive, targetArchiver Archiver) Archive {
        return targetArchiver.Archive(ctx, sourceArchive.File().Name(), sourceArchive.Dir())
}
full flint
#

I only wanted to demonstrate the issue with this example. The real use case is a bit more complicated, but may not actually work if the interface cannot be defined on the caller side.

I'm working on a releaser module that accepts a number of external dependencies:

  • (File|Directory)Builders yield files/directories when Build is called on them
  • Archiver gets a directory and returns an archive

The implementation of builders and archivers may be in other modules (for example a go-builder module may expose a FileBuilder that returns a binary)

#

What happens if I define the interface in both modules? (The caller and the one implementing the interface)

What if I define the interface in multiple modules (modules providing different builder functionality)?

#

Could those serve as a workaround?

#

(That would mean I have an Archiver interface in both the archivist and the releaser module)

spiral hamlet
#

Yeah right now I'd basically think of it as each module needs to define an interface for exactly what it wants to accept/return and use those as args/returns, but that would currently lead to sometimes overlapping or equal interface definitions in different modules.

In this particular case I do wonder if this would end up working:

  1. Builder implementations are defined in separate modules. They don't need to return or define an interface, they are just concrete struct implementations.
  2. Archiver implementations are also defined in separate modules, again just using concrete structs
  3. The releaser module is the one that actually uses interfaces, it defines the Archiver+Builder interfaces and can thus accept implemenations of those from a caller.

Then a caller will have deps on the Builder implementations it wants to use, Archiver implementations it wants to use, and on releaser and can pass all those to the releaser for use.

#

Not sure if that exactly fits what you were imagining, but that's the general structure that works best at the moment

full flint
#

Is the context parameter mandatory? I added an Archiver interface to the archivist module, but I still don't see AsArchiver :/

spiral hamlet
full flint
#

Yeah, that's certainly missing. ๐Ÿ™‚

full flint
full flint
#

Doesn't seem to work as a return value

spiral hamlet
# full flint

Right right, that's the other incarnation of the issue I linked in the beginning.

It's avoided in the structure I described above though, basically all the Archiver implementations just return their actual concrete struct value and then it's the releaser module that defines the Archiver interface so that it can accept arbitrary implementations.

#

(To be clear, none of this is obvious ๐Ÿ˜„ I appreciate you working through it)

full flint
#

To be clear: I added the Archiver interface to archivist and imported it in releaser (where I also defined an Archiver interface)

spiral hamlet
full flint
#

Well, it doesn't seem to work. Whether the interface is defined in archivist or not, there is no AsArchiver generated (in releaser). ๐Ÿ˜„

#

That's what my original example was supposed to demonstrate.

#

The only difference I can see is there is no third caller module at the moment. (Basically, I'm trying to set a default archiver implementation if the user doesn't define one)

#

Could that be the problem?

spiral hamlet
# full flint Could that be the problem?

Yeah I think so, but to try resolve ambiguity here's roughly what I was imagining:

archivist:

type Tar {}

func (m *Tar) Archive(name string, source *Directory) *File {
    return arc().ArchiveDirectory(name, source).Tar()
}

type TarBz2 struct{}

func (m *TarBz2) Archive(name string, source *Directory) *File {
    return arc().ArchiveDirectory(name, source).TarBz2()
}

// etc., no interface defined in this module

releaser (no dep on archivist):

type Archiver interface {
    DaggerObject
    Archive(ctx context.Context, name string, source *Directory) *File
}

func (m *Releaser) DoSomething(source *Directory, archiver Archiver) {
   // ...
}

caller module (dep on both archivist and releaser):

func (m *Caller) DoRelease() {
   // note the namespacing of "AsReleaserArchiver", admittedly a bit awkward in this case
   dag.Releaser().DoSomething(someSource, dag.Archivist().Tar().AsReleaserArchiver())
}
spiral hamlet
full flint
#

Hm, I think I understand. So if the interface definition is the same, I don't actually need AsArchiver in releaser, right?

full flint
#
func (m *Releaser) archiver(ctx context.Context, platform Platform) Archiver {
    // TODO: custom archiver

    if strings.HasPrefix(string(platform), "windows/") {
        return dag.Archivist().Zip()
    }

    return dag.Archivist().TarGz()
}

This seem to work (at least LSP doesn't complain)

#

Huh....this took some brain power to understand ๐Ÿ˜„

spiral hamlet
# full flint Huh....this took some brain power to understand ๐Ÿ˜„

Yeah totally, you are the first real user of it outside me afaik, so this is extremely helpful.

We definitely will get to fixing the issue I linked at the beginning, which should hopefully make everything somewhat more intuitive. It's trickier than it might sound since it requires codegen be based on both a modules own types in addition to its deps, but that's a general problem we need to tackle anyways.

#

Besides fixing that issue and obviously just having real docs + examples around this, if there's anything else that you think could have made it less of a brain-melting exercise, please let me know ๐Ÿ™‚

full flint
#

I think you set me on the right path, so I'll go ahead and give this releaser module a try. I think it's going to be a good real world use case for interfaces.

That example on the original PR did send me to a few deadends, so I probably wouldn't show that to people. ๐Ÿ˜›

#

A visual representation of how different modules relate to each other may help explaining where and why As* methods are generated (or where they are necessary)

spiral hamlet
full flint
#

I'll report back when I have something to show.

Thanks for the hand-holding!