- 
                Notifications
    
You must be signed in to change notification settings  - Fork 3.5k
 
          Add Mix.Tasks.Compile.reenable
          #13771
        
          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
  
    Add Mix.Tasks.Compile.reenable
  
  #13771
              Conversation
| 
           Can you please expand a bit more on the use case? Why are you reenabling the compiler? Also, if this is specific to the compiler, I would rather have a task for reenabling the compiler explicitly. The reason I ask is because I can see someone wanting to reenable some compilers but not all of them, and this could be as well a change of behaviour. Also remember that you can always get the list of compilers and individually enable the ones you care about.  | 
    
          
 I personally do reenable compiler because I have a task which generates a regular file from the template and then generates a test file based on the information which can be retrieved from the loaded module only. That said, before generating the test, I need the file to be compiled and loaded. 
 To my understanding, this is not specific to the compiler. I took it as an example, but the real issue is more generic. AFAICT,  
 This is already possible with an explicit reenabling specific compilers. This PR is not after allowing a fine-tuning over what compilers to execute. It is fully about making  
 As I hopefully clarified above, I understand this (also the example in the PR comment shows that,) but this PR is not about that. I am after the ability to make it possible to roll back my own task   | 
    
| 
           Thank you. If the compiler is not your use case, then I would like to hear a more concrete example. The ultimate issue is that the concept of reenable one and reenable all are both equally flawed.  If your goal is to make it behave as if they were separate CLI invocations, there is already   | 
    
          
 Well, the compiler is indeed my use-case, but I have a habit to read sources if something goes in any unexpected way, which is unfortunately not what 100% of developers would do in the first place. I must have expressed myself poorly, let me try again with another example below. 
 That is exactly why I didn’t propose gathering this info from the task source code and/or do it in any other automatical fashion. The particular task author would surely know if any other task is to be unlimately re-enabled within their own task.  
 Well, it would work, but I am trying to introduce a fine tuning. Please see an example below. 
 My proposed semantics does not imply any rules. It’s totally up to the task author what to reenable within their task. As you mentioned above,  Exampledefmodule Mix.Tasks.My.Config do
  use Mix.Task
  @impl Mix.Task
  def run([file]) do
    File.write!(file, …)
  end
end
defmodule Mix.Tasks.My.Test do
  use Mix.Task
  @impl Mix.Task
  def run(args) do
    {test, config} = args |> OptionParser.parse(…) |> reshape()
    Mix.Task.run("my.config", [config])
    Mix.Task.run("test", [test])
  end
end
defmodule Mix.Tasks.My.Tests do
  use Mix.Task
  @impl Mix.Task
  def run(args) do
    [{_test, _config} | _] = 
      test_configs = 
      args |> OptionParser.parse(…) |> reshape()
    Enum.each(test_configs, fn {test, config} ->
      # reenabling only this won’t work as expected
      # "my.config" won’t be re-enabled
      Mix.Task.reenable("my.test") 
      
      Mix.Task.run("my.test", ["--test", test, "--config", config])
    end)
  end
endThe code above might be made more user-friendly by  defmodule Mix.Tasks.My.Test do
   use Mix.Task
   @impl Mix.Task
   def run(args) do
     {test, config} = args |> OptionParser.parse(…) |> reshape()
     Mix.Task.run("my.config", [config])
     Mix.Task.run("test", [test])
   end
+  @impl Mix.Task
+  def depends_on do
+    ["my.config", "test"]
+  end
 endThis could be especially helpful if both  If the above does not convince you either, feel free to close the PR, because it seems I am the only one who entered into the necessity to dig the source code to figure out what to re-enable besides the task I really need to re-enable.  | 
    
          
 Yes, this is the concern in my first comment. It depends on the author and I am not sure if the default implementation of  Since your ultimate goal is to reenable the compiler, we can expose something   | 
    
| 
           OK, so I am to change this PR to: 
 Right?  | 
    
| 
           Just the second part. Add   | 
    
| 
           None is already covered, this task is in   | 
    
| 
           Sorry, none would not include compile.all. compile.all is always included, but you can choose the compilers. And FWIW, the Phoenix compiler does this: https://github.com/phoenixframework/phoenix/blob/main/lib/phoenix/code_reloader/server.ex#L247  | 
    
| 
           BTW, maybe   | 
    
        
          
                lib/mix/lib/mix/task.compiler.ex
              
                Outdated
          
        
      | """ | ||
| @spec reenable([{:compilers, compilers}]) :: :ok when compilers: :all | [Mix.Task.task_name()] | ||
| def reenable(opts \\ []) do | ||
| if not Keyword.keyword?(opts) do | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to check for the arguments.
        
          
                lib/mix/lib/mix/task.compiler.ex
              
                Outdated
          
        
      | case Keyword.get(opts, :compilers, []) do | ||
| :all -> Mix.Tasks.Compile.compilers() | ||
| list when is_list(list) -> list | ||
| end | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's go with all by default, because it is easier to customize.
| case Keyword.get(opts, :compilers, []) do | |
| :all -> Mix.Tasks.Compile.compilers() | |
| list when is_list(list) -> list | |
| end | |
| Keyword.get_lazy(opts, :compilers, &Mix.Tasks.Compile.compilers/0) | 
I am also thinking we should move this function to Mix.Tasks.Compile, since compilers is already defined there.
        
          
                lib/mix/lib/mix/task.compiler.ex
              
                Outdated
          
        
      | list when is_list(list) -> list | ||
| end | ||
| 
               | 
          ||
| Enum.each(["loadpaths", "compile", "compile.all"], &Mix.Task.reenable(&1)) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am unsure if we should enable loadpaths. Thoughts? Why not deps.loadpathsas well? That's one of the slippery slopes that worry me. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am unsure if we should enable loadpaths.
We should not. It is a leftover from the code when I thought the enabling should be mirrored by a roll-back, but it should not in the latest approach. I cannot think of any change resulting in a need to reload paths from within same external task execution, even if it calls reenable/1.
        
          
                lib/mix/lib/mix/task.ex
              
                Outdated
          
        
      | @spec reenable(task_name) :: :ok | ||
| def reenable(task) when is_binary(task) or is_atom(task) do | ||
| task = to_string(task) | ||
| def reenable(task) when is_atom(task) do | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove the changes to this function. :)
depends_on/0 callback to Task behaviourMix.Tasks.Compile.reenable
      | 
           There is one question remaining, which is about protocols consolidation. It is treated as a separate compiler, but it should most likely be reenabled too. There are a few things to consider: 
  | 
    
          
 I believe, this issue has nothing to do with Phoenix per se. 
 I am to start with changing tests to fail by changing direct calls to  
 That would be most likely the best resulting code of this PR, but first I want to make sure re-enabling protocols works as expected within current design (separated task for protocols.)  | 
    
| 
           Yeah. I mean the Phoenix PR is related to this one because Phoenix reenables the compilers, and I would be nice if Phoenix could use this API as well. If it can, it is a good indicator this is general, if not, it may be too specific. But in order to assess this, we need to fix current bugs.  | 
    
| 
           The plot thickens. 
 The cleanest approach I could come with, would be to enforce  The phoenix code could with this change to be simplified to   defp mix_compile(compilers, compile_args, config, consolidation_path) do
    compilers = config[:compilers] || Mix.compilers()
    protocols = if config[:consolidate_protocols], do: :consolidate
    Mix.Task.Compile.reenable(compilers: compilers, protocols: protocols)
    # We call build_structure mostly for Windows so new
    # assets in priv are copied to the build directory.
    Mix.Project.build_structure(config)
    args = ["--purge-consolidation-path-if-stale", consolidation_path | compile_args]
    result =
      with_logger_app(config, fn ->
        Mix.Task.run("compile", args)
      end)
    with :error <- result, do: exit({:shutdown, 1})
  endI receive the following error while running the complete test suite, but I am unsure if it’s related because no changed in this PR code could somehow affect the behaviour due to my best assessment:  | 
    
        
          
                lib/mix/lib/mix/tasks/compile.ex
              
                Outdated
          
        
      | "elixir", acc -> ["protocols", "elixir" | acc] | ||
| "app", acc -> ["app", "protocols", "elixir" | acc] | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If :elixir has been given, append :protocols right after it, prepend both :elixir and :protocols to :app otherwise, so that :app is still the latest compiler.
I am not sure if this is actually needed.
| # Delete a local protocol | ||
| File.rm!("lib/protocol.ex") | ||
| assert compile_elixir_and_protocols() == :noop | ||
| assert compile_elixir_and_protocols() == :ok | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:ok here and below is because :protocols now implies :elixir invocation which would return {:ok, []} not {:noop, []}. Checks for Compile.Protocol.__protocol__(:impls) now plays the role of real checker.
| 
           And this is going to change soon, because protocols will be bundled into the Elixir compiler. Perhaps we should prioritize that work to be on 1.18 and we can merge this with more confidence. Stay tuned!  | 
    
| 
           Apologies, this will be postponed to v1.19 since I was not able to get the compile.protocols refactor in time for v1.18.  | 
    
          
 No issue. Would you like me to try to help with compile.protocols? I understand at least 20% of the issues with it since I dug into this dependency hell when I first came out with this proposal. I might be missing a ton of internals, though, so I would be fine with “your approach sucks” as a result :)  | 
    
| 
           No need, thanks, since I still need to figure out how exactly it will look like.  | 
    
          
 Sometimes the look from the other bench helps. OK, just shout out when you have run out of ideas.  | 
    
| 
           Hi @am-kantox. This is ready to be revisited: 
 Thank you.  | 
    
Co-authored-by: José Valim <[email protected]>
cbcede4    to
    fa2a19e      
    Compare
  
    
          
 I believe the latest commit is what we are after. 
 There is a test failing in lib/mix/test/mix/tasks/release_test.exs:716 with a timeout but I guess it’s unrelated.  | 
    
| 
           Looks great. Can you also please change IEx.Helpers.reenable_tasks(config) to use this new function? :)  | 
    
Co-authored-by: José Valim <[email protected]>
Co-authored-by: José Valim <[email protected]>
Co-authored-by: José Valim <[email protected]>
          
 Done, as well as the test for task/compiler given as an atom.  | 
    
| 
           💚 💙 💜 💛 ❤️  | 
    
Mix.Task.reenable/1does not currently re-enable tasks which were implicitly invoked fromMix.Task.run/1.For instance, when some task creates the new file within
libdirectory, the following code works for"compile.elixir"but results in{:noop, []}for"compile".This PR introduces an optional callback
depends_on/0for bothMix.TaskandMix.Task.Compilerwhich is used to recursively re-enable tasks this one depends on.