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

Refactor and reuse code #101

Merged
merged 24 commits into from
Jul 25, 2022
Merged

Refactor and reuse code #101

merged 24 commits into from
Jul 25, 2022

Conversation

Bluebugs
Copy link
Contributor

@Bluebugs Bluebugs commented Apr 6, 2022

Description:

This is "just" a refactor. As I was working on #98, I realize it would be simpler if there was a bit more potential for reusing code around. I am not sure that all of those change are really idiomatic go and feel like doing a PR that "just" move code around and remove duplicated step will make the next PR easier to move forward.

Checklist:

  • Tests included.
  • Lint and formatter run with no errors.
  • Tests all pass.

Where applicable:

There should be no functional change. I have only the ability to test with podman at the moment and will be glad if anyone tried with docker.

@Bluebugs Bluebugs requested review from lucor and Jacalz April 6, 2022 17:40
@lucor
Copy link
Member

lucor commented Apr 7, 2022

I had a cursory look at PR and I would say that the refactor make sense to me but some thoughts come in mind... :-)
Looking at the amount of code changed I was thinking if it could be the right time to move fyne-cross as command of the fyne CLI app, something like fyne cross.
Having it as part of the fyne CLI will bring the following pros:

Not strictly related but something could be helpful is to move the Docker image code into a dedicated repository (i.e. fyne-cross-image). Right now it they are different projects with a proper life-cycle that shares the same repo.

Thoughts?

@Bluebugs
Copy link
Contributor Author

Bluebugs commented Apr 7, 2022

I personally like the idea of moving fyne-cross to become fyne cross and get synchronized release. It will have to be discussed with the rest of the community. Maybe we should create a set of issues toward that goal in fyne-cross and aim for inclusion of fyne-cross in fyne for 2.3.

Jacalz
Jacalz previously approved these changes Apr 9, 2022
Copy link
Member

@Jacalz Jacalz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect that @lucor will have more (if anything) to say about the code. It all looks sensible to me but I don't quite have the same knowledge over the codebase. All my local builds of rymdport also work just as before, hence approved :)

@andydotxyz
Copy link
Member

could be the right time to move fyne-cross as command of the fyne CLI app, something like fyne cross.

This is a great thing to work towards, however I don't think I would have it as a separate command (fyne cross) but more likely an extension of the whole command where we improve the cross-compile system and allow the fyne-cross path to be run if/when appropriate.

@lucor
Copy link
Member

lucor commented Apr 11, 2022

Good point, that makes sense to me. We already do a similar thing when building from a macOS with a darwin target

Copy link
Member

@lucor lucor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had a little in depth look at the code and the most of the feedback are related about the naming like: ContainerImage, AllContainerImage, AllContainerRunner.

Anyway all changes are internal and it works locally using docker as before, so feel free to merge if we need it for #108. I'd not block this with a discussion about naming :) Probably they will be lost during the porting to fyne CLI and in any case we can always address them in to a later stage.

)

type ContainerRunner interface {
NewImageContainer(arch Architecture, OS string, image string) ContainerImage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about having a constructor defined as part of an interface

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, that isn't the Go pattern, It is not clear what ContainerRunner is describing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it, ContainerRunner is basically the engine definition (podman, docker, kubernetes).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so Engine could work, remove the duplication that's tripping me up?
Could use Create instead of New so it's not looking like a constructor?
Also I realise here there is ImageContainer vs ContainerImage - which is desired?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ContainerImage.

Comment on lines 25 to 26
Debug(v ...interface{})
GetDebug() bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about moving these methods into a dedicated interface, like Debugger ?

Comment on lines 46 to 53
GetArchitecture() Architecture
GetOS() string
GetID() string
GetTarget() string
GetEnv(string) (string, bool)
SetEnv(string, string)
SetMount(string, string)
AppendTag(string)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to add these methods as part of an interface ? Should be they removed in favor of a contract with the AllContainerImage struct ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds doable. I was thinking I would have to change the behavior of some of them, but actually not at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember that in Go it is idiomatic to remove Get prefix for simple getter calls.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the Os/Arch part of the API at all? a ContainerImage is a generic construct that is created by the ContainerRunner calling some constructor passing in state, so this feels a little circular.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After trying to remove them, it actually is more useful than expected as code use a ContainerImage to build figure out what command to execute. A ContainerImage is a representation of an Engine/OS/Architecture capability. I think they are for the moment useful where they are.

@lucor
Copy link
Member

lucor commented Apr 14, 2022

@Bluebugs it looks like the android command is not available:

The commands are:

	darwin-image  Build the docker image for darwin
	darwin        Build and package a fyne application for the darwin OS
	linux         Build and package a fyne application for the linux OS
	windows       Build and package a fyne application for the windows OS
	              
	ios           Build and package a fyne application for the iOS OS
	freebsd       Build and package a fyne application for the freebsd OS
	version       Print the fyne-cross version information

Use "fyne-cross <command> -help" for more information about a command.```

@Bluebugs
Copy link
Contributor Author

@Bluebugs it looks like the android command is not available:

Indeed, good catch. I missed that part. Will fix it shortly.

@andydotxyz
Copy link
Member

I have added some thoughts around naming. However I was not able to add suggestions in most places as it is not clear of the intent from model presented. Can you please summarise what each of the new interfaces/structs are supposed to describe in one comment so we can understand the bigger picture @Bluebugs? It is a bit confusing that runners contain images bit images have run methods.

@Bluebugs
Copy link
Contributor Author

Sounds like a lot of good suggestion. I can have a pass at it tomorrow and we can iterate on the change after that.

@Bluebugs
Copy link
Contributor Author

Bluebugs commented May 11, 2022

Small attempt at a diagram, not sure it is super usable:

classDiagram
   Command<|--Linux
   Command<|--Windows
   CrossBuilder<|--Linux
   CrossBuilder<|--Windows

   class Command{
     <<Interface>>
     +Run() error
   }
   class CrossBuilder{
     <<Interface>>
     +Build(ContainerImage)
     -setupContainerImage(flags, args) error
   }

   Command-->ContainerImage:Run() Use
   CrossBuilder-->ContainerImage:Build() Use
   CrossBuilder-->ContainerEngine:setupContainerImage() Use

   ContainerEngine<|--LocalContainerEngine
   ContainerImage<|--LocalContainerImage
   ContainerEngine<|--KubernetesContainerEngine
   ContainerImage<|--KubernetesContainerImage

   class ContainerEngine{
     <<Interface>>
     +createContainerImage(arch Architecture, OS string, image string) containerImage
   }

   class ContainerImage{
     <<Interface>>
     +Run(vol volume.Volume, opts options, cmdArgs []string) error
   }

   ContainerEngine-->ContainerImage:create
Loading

@andydotxyz
Copy link
Member

That is really helpful thanks - thought it is missing the PlatformBuilder which was part of my confusion from earlier.
Apart from that the diagram makes sense - if it included the Fields that were of the object types as well we might see the circular relationships emerge.

@Bluebugs
Copy link
Contributor Author

Adding platformBuilder which is being used by CrossBuilder to provide per platform specific build step (The OS specific structure also rely on CrossBuilder to provide actually a portion of the Command interface that was duplicated across all OS before). I think the arrow for most field that have structure or interface are actually visible here.

classDiagram
   Command<|--Linux
   Command<|--Windows
   platformBuilder<|--Linux
   platformBuilder<|--Windows

   class Command{
     <<Interface>>
     +Run() error
   }
   class CrossBuilder{
     <<Interface>>
     +platformBuilder
     +Run()
     -setupContainerImage(flags, args) error
   }
   class platformBuilder{
     <<Interface>>
     +Build(ContainerImage)
   }

   CrossBuilder-->ContainerImage
   CrossBuilder-->ContainerEngine:setupContainerImage() Use
   CrossBuilder-->platformBuilder:Build() Use
   platformBuilder-->ContainerImage
   Command<|--CrossBuilder

   ContainerEngine<|--LocalContainerEngine
   ContainerImage<|--LocalContainerImage
   ContainerEngine<|--KubernetesContainerEngine
   ContainerImage<|--KubernetesContainerImage

   class ContainerEngine{
     <<Interface>>
     +createContainerImage(arch Architecture, OS string, image string) containerImage
   }

   class ContainerImage{
     <<Interface>>
     +Run(vol volume.Volume, opts options, cmdArgs []string) error
   }

   ContainerEngine-->ContainerImage:create
Loading

@Bluebugs Bluebugs requested a review from andydotxyz May 24, 2022 14:28
@@ -59,6 +58,7 @@ type containerImage interface {
Tags() []string

Engine() containerEngine
Debugger() debugger
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks more like moved rather than removed. In this new position all containerImages must be debuggable. Is this a desirable definition of an image?
Go loose typing indicates that anything could be debuggable, if it provides the facilities to be debugged, rather than stating which interfaces will have to support debugging...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum, I am not sure that I get what you want correctly. Do you mean, I should always cast all Debugger call on the Engine()? Something like: debug, ok := image.Engine().(Debugger) and then if the debugger is present do call the debugger function?

@andydotxyz
Copy link
Member

Adding platformBuilder which is being used by CrossBuilder to provide per platform specific build step

Thanks for this, I think I am starting to understand.
Proposal: CrossBuilder is not an interface, it is our single struct type that is the core of this package. It would use a PlatformBuilderandContainerEngine` to function.

The OS specific structure also rely on CrossBuilder to provide actually a portion of the Command interface that was duplicated across all OS before

This feels messy - combining re-use with inheritance. Why not separate the requirement on CrossBuilder with some "command common code" type (I don't know what scope this has) that contains only this common code? Remember that Go supports multiple inheritance. This way the platform builders don't depend on the cross builder, which depends on them again.

I hope these ideas are helpful, It should take out some of the seemingly circular usages.

@Bluebugs
Copy link
Contributor Author

Adding platformBuilder which is being used by CrossBuilder to provide per platform specific build step

Thanks for this, I think I am starting to understand. Proposal: CrossBuilder is not an interface, it is our single struct type that is the core of this package. It would use a PlatformBuilderandContainerEngine` to function.

It is pretty much this already, except it use a ContainerImage to function which is provided by a ContainerEngine.

The OS specific structure also rely on CrossBuilder to provide actually a portion of the Command interface that was duplicated across all OS before

This feels messy - combining re-use with inheritance. Why not separate the requirement on CrossBuilder with some "command common code" type (I don't know what scope this has) that contains only this common code? Remember that Go supports multiple inheritance. This way the platform builders don't depend on the cross builder, which depends on them again.

I kind of want to avoid touching much of the command part as it will disappear, I believe, once we migrate to cli.App.

@andydotxyz
Copy link
Member

The OS specific structure also rely on CrossBuilder to provide actually a portion of the Command interface that was duplicated across all OS before

This feels messy - combining re-use with inheritance. Why not separate the requirement on CrossBuilder with some "command common code" type (I don't know what scope this has) that contains only this common code? Remember that Go supports multiple inheritance. This way the platform builders don't depend on the cross builder, which depends on them again.

I kind of want to avoid touching much of the command part as it will disappear, I believe, once we migrate to cli.App.

I see - but how does the current cli library require that each platform-specific builder rely on the cross builder?

@Bluebugs
Copy link
Contributor Author

I see - but how does the current cli library require that each platform-specific builder rely on the cross builder?

The cross builder is the provider of Run() which really shouldn't be duplicated, but due to the Cli library requiring the existence of a Run per platform it has to be somewhere. The solution is to either duplicate code (bad) or reuse by depending on cross builder (better). Best will be when we do not need to link the crossBuilder.Run() with the Cli and can then just drop that from the dependencies.

@Bluebugs Bluebugs requested review from andydotxyz June 3, 2022 16:01
@andydotxyz
Copy link
Member

After discussing debugger on the call this is closer but I am still stuck on :

The solution is to either duplicate code (bad) or reuse by depending on cross builder (better).

isn’t there a third option where they don’t have to depend on one another but the common code is used by both?

@Bluebugs
Copy link
Contributor Author

Bluebugs commented Jun 3, 2022

The solution is to either duplicate code (bad) or reuse by depending on cross builder (better).

isn’t there a third option where they don’t have to depend on one another but the common code is used by both?

I can not think of a different solution until we use cli.App that would be significantly different from any of the one above. I am convinced that using cli.App will solve this problem and that we won't need anymore that duplicated code, but switching to cli.App introduce massive change in the code base and this PR is already big enough that I do not want to do that here. Still I think that that would likely address your problem completely and be your third option.

@Bluebugs Bluebugs force-pushed the refactor-reuse-code branch from f626acd to 071d846 Compare June 3, 2022 23:14
@andydotxyz
Copy link
Member

Please don't force push after reviews started, it now needs a complete review again as we can't see the changes any more

@Bluebugs
Copy link
Contributor Author

Bluebugs commented Jun 6, 2022

Please don't force push after reviews started, it now needs a complete review again as we can't see the changes any more

Sorry about that. I made a mistake in merging the wrong branch and I had to force push to undo that change.

@andydotxyz
Copy link
Member

I had a look at the code and am not sure I agree that crossBuilder must exist because of the way the CLI works.
I see that it removes duplication of shared code, but I don't think it's the right way to do it because crossBuilder and platformBuilder create a tight circular dependency.

Looking at it there are 3 methods, two if which just return the name and description, which is different. The third, Run does all of the hard work. So why not:

  • return Name and Description back to the commands (platform builder entry points)
  • move the crossBuilder.Run to a helper method and remove crossBuilder completely
  • call the helper run method from each platform builder Run

This should be approx the same number of lines as you have, but with a cleaner control dependency graph.

@Bluebugs
Copy link
Contributor Author

The build failure is unrelated as far as I know and due to a bug with v2.2

@andydotxyz
Copy link
Member

The build failure is unrelated as far as I know and due to a bug with v2.2

Or maybe it's not a bug and this is now using Fyne v2.2.1 tools to compile a Fyne v2.1.4 app - we do ask that folk keep the versions in sync.

Probably we didn't see this coming but it is a side/effect of using the generated code to insert metadata. I guess we could improve the tooling to try and detect if SetMetadata is available.

@andydotxyz
Copy link
Member

I have updated the calculator module and kicked the tests so we can see if that was the only error.

Copy link
Member

@andydotxyz andydotxyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy with how this turned out. I'd like to know that @lucor is too though :)

Copy link
Member

@lucor lucor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, additionally I won't make this blocker. The refactoring is related to internal packages and we can always iterate later, if needed.

@andydotxyz
Copy link
Member

Perhaps we should make the current release, then land it to work on with more time?

@Bluebugs Bluebugs merged commit 1c93082 into develop Jul 25, 2022
@Bluebugs Bluebugs deleted the refactor-reuse-code branch July 25, 2022 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants