Flow.Launcher icon indicating copy to clipboard operation
Flow.Launcher copied to clipboard

Hot Reload Plugins

Open HorridModz opened this issue 1 year ago • 16 comments

I'm developing a plugin for FlowLauncher, and of course, the process involves frequently building and reloading the plugin. This is pretty annoying, as I can't just build the plugin. Instead, I have to exit FlowLauncher, build the plugin, open the plugin's directory in FlowLauncher's UserData folder and the folder which my plugin has been built to, copy the files over, and restart FlowLauncher.

Describe the solution you'd like

It would be great if there was a way to hot reload plugins from a specified folder, specifically for development purposes. I would love if FlowLauncher let me run a command like plugin reload <plugin> <plugin_folder>, and it would reload the plugin using the specified folder instead of the plugin's UserData folder. It couldn't directly reload from the UserData folder, though, because that would require first killing FlowLauncher so the folder could be edited - thus killing the point.

Describe alternatives you've considered

FlowLauncher has a command Reload Plugin Data, which reinitializes all plugins. However, editing the plugins in their UserData folder requires first killing FlowLauncher so the folder could be edited, so for development it's useless. And anyway, it's still a pain to have to copy the files into the UserData folder.

I could make a script to automate the process - I'm on the verge of doing that right now. But I think that a feature like this should inherently be included, as I'm sure I'm not the only plugin developer who finds this an inconvenience.

HorridModz avatar Jun 19 '24 00:06 HorridModz

I also faced this issue when I started developing plugins for myself. I found two possible solutions:

  1. I made it so the build configuration in my IDE builds into FlowLauncher\Plugins\PluginName directly. That way I don't have to copy anything manually, I just need to stop Flow Launcher, build my plugin, run Flow Launcher again. This is the solution I use because if anyone else were to build my plugins, they wouldn't need to edit anything.
  2. You could theoretically add something like this to your .csproj file:
      <Target Name="PreBuild" Condition="'$(Configuration)' == 'Debug'" BeforeTargets="PreBuildEvent">
        <Exec Command="taskkill /f /fi &quot;IMAGENAME eq Flow.Launcher.exe&quot;" />
      </Target>
    
      <Target Name="PostBuild" Condition="'$(Configuration)' == 'Debug'" AfterTargets="PostBuildEvent">
        <Exec Command="start &quot;&quot; /d C:\Users\YOUR_USERNAME\AppData\Local\FlowLauncher\ Flow.Launcher.exe" />
      </Target>
    
    This will stop Flow Launcher process before build (if you're building with debug configuration), build the plugin, start Flow Launcher again. I don't know if it's a good idea or not. Anyone trying to build your plugin themselves would have to adjust the path before they can build with debug configuration.

Both of these solutions are not great, but it's the best I've found without Flow Launcher natively supporting hot reload.

Yusyuriv avatar Jun 19 '24 01:06 Yusyuriv

I also faced this issue when I started developing plugins for myself. I found two possible solutions:

  1. I made it so the build configuration in my IDE builds into FlowLauncher\Plugins\PluginName directly. That way I don't have to copy anything manually, I just need to stop Flow Launcher, build my plugin, run Flow Launcher again. This is the solution I use because if anyone else were to build my plugins, they wouldn't need to edit anything.

  2. You could theoretically add something like this to your .csproj file:

      <Target Name="PreBuild" Condition="'$(Configuration)' == 'Debug'" BeforeTargets="PreBuildEvent">
        <Exec Command="taskkill /f /fi &quot;IMAGENAME eq Flow.Launcher.exe&quot;" />
      </Target>
    
      <Target Name="PostBuild" Condition="'$(Configuration)' == 'Debug'" AfterTargets="PostBuildEvent">
        <Exec Command="start &quot;&quot; /d C:\Users\YOUR_USERNAME\AppData\Local\FlowLauncher\ Flow.Launcher.exe" />
      </Target>
    

    This will stop Flow Launcher process before build (if you're building with debug configuration), build the plugin, start Flow Launcher again. I don't know if it's a good idea or not. Anyone trying to build your plugin themselves would have to adjust the path before they can build with debug configuration.

Both of these solutions are not great, but it's the best I've found without Flow Launcher natively supporting hot reload.

Gee, thanks! I said I was on the verge of making a script to automate the process, but I guess you already have! Obviously not a solution, like you said, but at least now I have something to help.

HorridModz avatar Jun 19 '24 16:06 HorridModz

Hotreload is hard. We have so many interdependencies of the current plugin system. I could share some resource if anyone would like to tackle the potential of reloadable plugins. Probably they will need to be loaded differently and with different features set.

taooceros avatar Jun 19 '24 19:06 taooceros

Hotreload is hard. We have so many interdependencies of the current plugin system. I could share some resource if anyone would like to tackle the potential of reloadable plugins. Probably they will need to be loaded differently and with different features set.

Interesting; can you elaborate on what exactly is meant by "interdependencies"? Because, with my limited understanding as a plugin author, I can't see why replacing the functions in my plugin's code would affect anything else. Anyway, if it did, it would be possible to reload all of the affected plugins from their normal directories (like the existing Reload Plugin Data command does) after hot reloading the designated plugin. Just some thoughts, but correct me as I'm probably missing something.

HorridModz avatar Jun 19 '24 21:06 HorridModz

Basically the current method I know about hot reloading plugin requires flow to cleanup all references to the plugin assembly. However that seems very hard when I tried it years ago. https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability

taooceros avatar Jun 19 '24 21:06 taooceros

Basically the current method I know about hot reloading plugin requires flow to cleanup all references to the plugin assembly. However that seems very hard when I tried it years ago. learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability

Interesting. So you're making it sound like with all the effort / required reloading, you might as well just reload FlowLauncher and hot reloading is really not worth it.

HorridModz avatar Jun 20 '24 17:06 HorridModz

Yeah that's what I am thinking. However it is certainly possible that I am not knowledgeable enough to make this feature.

On the other hand, hotreload doesn't gain us too much. Restarting flow with some script copying the plugin around does not consume too much time and resource. It is also possible to attach the ide automatically.

.net does have a hotreload feature via dotnet watch. However I don't know whether it is possible to adapt it to flow...

taooceros avatar Jun 20 '24 17:06 taooceros

By the way, I've been trying to make my silly post-build script work for my FlowLauncher, which is tough because it runs as admin. I ended up with this, which works in terminal but for some reason exits with error code 1 in visual studio, so I'll dump it here. Not that it matters or anything.

<!--
	Post-build script to make sure FlowLauncher is closed, copy build output to plugin path, and restart FlowLauncher when building as Debug
	Make sure to close FlowLauncher before building
	-->
	<Target Name="PostBuild" Condition="'$(Configuration)' == 'Debug'" AfterTargets="PostBuildEvent">
		<Exec Command="@tasklist /FI &quot;IMAGENAME eq Flow.Launcher.exe&quot; 2&gt;NUL | find /I &quot;Flow.Launcher.exe&quot; &gt;NUL &amp;&amp; (echo Cannot copy files to plugin directory - Flow.Launcher.exe is running. Close FlowLauncher and rebuild. &amp; exit /b 1) &amp;&amp; copy &quot;C:\Users\zachy\VSCodeProjects\Flow.Launcher.Plugin.Add2Path\bin\Debug&quot; &quot;C:\Users\zachy\AppData\Roaming\FlowLauncher\Plugins\Add2Path-1.0.0&quot; /Y &amp;&amp; &quot;C:\Users\zachy\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Flow Launcher\Flow Launcher.lnk&quot;" />
	</Target>

HorridModz avatar Jun 20 '24 17:06 HorridModz

I think you can task kill flow?

taooceros avatar Jun 20 '24 17:06 taooceros

Maybe when you test plugin you don't have to run flow as admin. I am not sure when would you need to though.

taooceros avatar Jun 20 '24 17:06 taooceros

Maybe when you test plugin you don't have to run flow as admin. I am not sure when would you need to though.

Yes, but unfortunately my plugin needs it because it is working with system environment variables. It's a special case.

HorridModz avatar Jun 24 '24 16:06 HorridModz

What about run visual studio as admin?

taooceros avatar Jun 24 '24 18:06 taooceros

What about run visual studio as admin?

Doesn't carry over to build commands. Of course, it's pretty easy to just make powershell scripts for pre and post build and call then from the csproj pre / post build commands. Not as elegant, but it's a workaround and seems to be the best you can do.

HorridModz avatar Jun 26 '24 21:06 HorridModz

@taooceros Would you like me to close the issue or keep it open? Or maybe assign a label. This is obviously just a pipe dream, so it might be better to just close it.

HorridModz avatar Jun 26 '24 21:06 HorridModz

I add a lebel for help wanted. We can leave it open I think.

taooceros avatar Jun 26 '24 23:06 taooceros

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 60 days.\n\nAlternatively this issue can be kept open by adding one of the following labels:\nkeep-fresh

github-actions[bot] avatar Aug 26 '24 01:08 github-actions[bot]

I've used McMaster's DotNetCorePlugins for easy hotreloading. Only a 33kb dll, if that's a concern.

If the plugins have their own dependencies adding a <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> to the plugin csproj has been enough.

For conflicting/redundant dependencies I've had some luck with controlling how they assemblies are resolved, something like:

    AssemblyLoadContext pluginLoaderContext = new AssemblyLoadContext("PluginLoaderContext", true);
    pluginLoaderContext.Resolving += (context, assemblyName) =>
    {
        var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetName().Name == assemblyName.Name
            && a.GetName().Version.CompareTo(assemblyName.Version) >= 0);

        return assemblies.LastOrDefault();
    };

aquafir avatar Aug 30 '24 23:08 aquafir