We want to run custom linting rules on project structure using tuist graph which will ensure that our dependencies are setup correctly. Certain dependencies are not imported in some particular modules to avoid an issue for downstream consumers etc… There are many other applications (like structuring a white label project for a specific app or generating code etc.)
Proposals:
Middlewares - A tuist manifest awaitable handler that tuist executes before each generate command and the handler returns a success or throws an error. If error, tuist prints it and terminates the process
let project = Project(
....,
middleware: { graph in
/// We probably also want to import SwiftSyntax or other internal libraries
await validateGraph(graph)
}
)
Custom Tuist Commands - Allow consumers to implement their own custom tuist commands (something like plugins but it doesn’t need to be a separate swift package of itself)
Pre/Post Hooks - At callsite we can can pass the command a custom script hook that the command can call before or after the (generate) command is executed
The properties of Projectmust be Codable, so passing in a function is a no-go.
As for 2. and 3., we have considered both options in the past, but I personally don’t see a ton of value in having hooks or extending the tuist CLI with custom commands over wrapping tuist and tuist generate in your own scripts that your team would be using.
From the three, pre- and post-generation hooks feel like the best option if we do decide to go ahead with something here, but again, I wonder how much of an improvement that is over having a custom script such as:
# pre-hook
echo "Here do pre-generate work"
tuist generate
# post-hook
echo "Here do post-generate work"
For scripts/hooks, the question is how do we get access to the graph? Would it be manual via. graph package? Isn’t project generated already if we want to use XcodeGraph?
Mutations are not possible though. This form of dynamism is discouraged because makes things unpredictable and that comes with some cascading effects in the Tuist workflows.
It does mean you’d need to load the graph twice. I wonder if we should do the same as we did with tuist share and add a --json option. You could then run tuist generate --json and get the resulting graph as the only output, so you could easily pipe it into further automation scripts.
I have implemented a bit of dynamism into the project generation process by generating our own dependency graph. I also think this can be useful in some cases as it makes it faster to fail if the graph is not been setup according to custom project parameters, or for creating custom aggregated schemes (that’s one of the things we do with it). I dont think this is too different from for example running tuist graph --json and passing the results through an environment variable to tuist generate. The difference would be is that I think it all being one step would make it faster and more intuitive? If it were to be part of the generation process then developers would have a guaranteed and standard way to access it.
Making validation convenient is a fair point. I’d not go beyond (Graph) -> Error? though, for example, to allow graph mutations, like CocoaPods allowed with hooks.
Enabling this one is a bit involved, but if anyone is up for it, feel free.
I propose that a validator is a Swift file with easy access to the graph (e.g., Graph.fromEnv()). Validators can live in a conventional directory structure, and Tuist runs them in order.
XcodeGraph can be distributed as a dynamic framework, such that validators can import XcodeGraph.
We’ll have to extend the following workflows:
Generation will compile and run them to ensure we do some caching.
Project editing should include a validator so that people can edit them easily.
This would be ideal, we would like to start making certain dependencies illegal in certain targets, so having access to the graph like this for validation would be very convenient
I understand the desire to come up with something more scoped, so people don’t do crazy things in their post-generation hooks – but I wonder if it would be more straightforward both from the implementation standpoint and from the user perspective if we added support for post-generation hooks as has been proposed a bunch of times in the past.
The post-generation hook would receive the whole graph as an argument, so you could do your validations. And if there’s a different need than validations, you could do that, too! Obviously, you shouldn’t try to do things like updating files as that would break some functionality like caching. But note that you’d be able to do that with Validators anyway – since validators would be a custom script, you could do anything there and we’d have no direct way to restrict you from that. So, the only reason why validators would be somewhat safer than post-generation hooks is the naming convention … which feels a bit weak to me.