#@Erik Sipsma I can't seem to make
1 messages ยท Page 1 of 1 (latest)
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())
}
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
Buildis 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)
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:
Builderimplementations are defined in separate modules. They don't need to return or define an interface, they are just concrete struct implementations.Archiverimplementations are also defined in separate modules, again just using concrete structs- The
releasermodule is the one that actually uses interfaces, it defines theArchiver+Builderinterfaces 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
Is the context parameter mandatory? I added an Archiver interface to the archivist module, but I still don't see AsArchiver :/
You'd need to actually use Archiver somewhere in the the archivist module, as an arg or a return. Just something to double check first
Yeah, that's certainly missing. ๐
This is exactly what I have in mind. I may want to have some default implementations or useful wrappers in the future (though I know I can't use the interface from where it's defined at the moment)
Though this certainly limits the usability of interfaces (for example: I may want to implement multiple interfaces: Archiver/Unarchiver)
Doesn't seem to work as a return value
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)
To be clear: I added the Archiver interface to archivist and imported it in releaser (where I also defined an Archiver interface)
Right, what I'm saying is that archivist can actually just return concrete structs for Tar and all the other archive types. It shouldn't actually need to define or use an Archiver interface, it should only be releaser that needs to do that.
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.
I added an Archiver interface to archivist based on your above comment: https://github.com/sagikazarmark/daggerverse/pull/40
But I'm still trying to use it in the context of the releaser module. That's where I would expect to see an AsArchiver generated on dag.Archivist().Tar()
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?
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())
}
I see, yeah in that case releaser is free to have a dep on archivist and use one of the implementations there as the default implementation of Archiver. Same basic idea as above still
Hm, I think I understand. So if the interface definition is the same, I don't actually need AsArchiver in releaser, right?
Exactly
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 ๐
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 ๐
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)
Haha totally ๐ I think the analogy is that the Pond is your Releaser and the Mallard/Crested are your Tar/TarGz/etc. But that's clearly not very obvious. Honestly your use case seems like a great real-world example, I may end up re-using something from it in our docs on all this
I'll report back when I have something to show.
Thanks for the hand-holding!