IPostprocessBuildWithReport and QA embarrasing answer about a serious bug

I reported the following bug:

Answer from QA:

The API:

This object has the info:

I think it’s embarrassing that answer. I mean, you telling me that depending on X it’s called or it’s not called. Then call it “IPostprocessSuccessfulBuildWithReport” to make its behavior clear.

In my case I want to do operations that depend on whether or not it fails and you are saying you can’t guarantee that behavior.

This API is very confusing, there is nothing documented on that subject although in fact it is not something to document but to correct so that it works as it should and always guarantees your call regardless of the step in which it fails.

You’re seriously telling me it’s “by design” and it’s not going to be corrected?

5 Likes

I totally agree with it. This makes the “do something in Preprocess and undo it in Postprocess” completely impossible.

3 Likes

Good morning all,
I’m with the Build Pipeline & Asset Bundle team in charge of this behavior, and also the one that made that decision, not QA. These callbacks were implemented to allow developers to hook into the build pipeline to do work on the build in progress. Such as modify scenes being built with the IProcessSceneWithReport callback, or manipulate the built assemblies with the IPostBuildPlayerScriptDLLs callback.

When a build fails, we don’t continue working on the build as that just doesn’t make sense. That work is a waste of time and cycles to developers because the build is unusable, for whatever reason. In fact, failing a build quickly is so important, we get quite a substantial number of bugs around how to make it fail even faster.

From what I understand based on the Fogbug is that you want to read information from the BuildReport after the build has been complete, success or failure. Have you looked at using this delegate: Unity - Scripting API: BuildPlayerWindow.RegisterBuildPlayerHandler This function allows you to hook into the build at the call site, so your code wraps the BuildPipeline.BuildPlayer api, and thus have full access to the returned BuildReport in it’s final state.

3 Likes

@Ryanc_unity in case of a failure, is there a BuildReport file stored to disk (under whatever folder it is usually saved, forgot the details now) ?

That’s not enough. From what I understand from the docs, that callback is called before the build happens, and only allow to change the options for the build, but not to stop it completely which would be the only way to make a custom call to BuildPlayer API and thus hace access to the return value.
Correct me if I’m wrong, but basically that callback provides another oportunity to do a PreProcess, but not a PostProcess.

Ya, the last build report is stored in the Library folder. I think the file is called lastBuildReport. Have to double check, having issues with my work pc, will follow up in a bit.

This is not a callback, think of this as an override. If you connect this delegate to your function, but your function doesn’t call BuildPipeline.BuildPlayer, or BuildPlayerWindow.DefaultBuildMethods.BuildPlayer, the player will not actually be built. So you have full control at this point to change options for the build, do pre-build setup, call the build api, then post-build cleanup.

Ok, computer data recovered…close call.
The last build stores it’s build report here: “Library/LastBuild.buildreport”

While this is a complete hack, I’ve come up with the following solution if you’re still looking at implementing something depending on success / failure / cancellation of a build. Hoping the Build Pipeline team at Unity will reconsider the IPostProcessBuildWithReport interface only working when a build successfully completes so that our build automation isn’t full of these hacks. The fact that I have to use the IPreprocessBuildWithReport to do something PostBuild is just bad design.

    void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report)
    {
        WaitForBuildCompletion(report);
    }


    static async void WaitForBuildCompletion(BuildReport report)
    {
        while (report.summary.result == BuildResult.Unknown)
        {
            //some arbitrary about of time meanwhile we wait for the build to complete, can't be bothered to find a better solution.
            await Task.Delay(1000);
        }

        switch (report.summary.result)
        {
            case BuildResult.Cancelled:
                Debug.Log("Build Cancelled");
                break;
            case BuildResult.Failed:
                Debug.Log("Build Failed");
                break;
            case BuildResult.Succeeded:
                Debug.Log("Build Succeeded");
                break;
        }
    }
7 Likes

I’d also like to request this as a new feature introduced to the BuildPipeline: A callback that is invoked either on build failure, or always, but with a reliable status of success, cancelled and failed.

There are so many use cases for having to know that a build failed. Just to name one: I create custom assets OnPreprocessBuild so that they are added to the player, but if this fails, I need to remove them again from the workspace. Works fine on successful build via OnPostprocessBuild, but not if there were any compilation errors (or something else might have gone wrong).

I don’t think wrapping Unity’s build methods is the right way to go, because this would limit us to a single client call. But what if I have multiple tools needing such a callback? Sure I do manage my own build process for the build server CI, but developers also use Build and Run for testing, and so I also need to handle this. Obviously, Unity logs “build failed” to the console, so they could also turn this into an event, just as a naive idea…

2 Likes

I have a bit of a hack for this that seems to work on Unity 2020.2.
You can register for the next editor update when building. If the build succeeds, OnPostprocessBuild is called before BuildCheck. This is likely not a very intentional (or possibly even deterministic) pattern, but it may be worth looking in to.

       public void OnPreprocessBuild(BuildReport report)
        {
            // Create Files
            Debug.Log("preprocess");
            EditorApplication.update += BuildCheck;
        }

        private void BuildCheck()
        {
            Debug.Log("building " + BuildPipeline.isBuildingPlayer);
            if (!BuildPipeline.isBuildingPlayer)
            {
                EditorApplication.update -= BuildCheck;
                OnPostprocessBuild(null);
            }
        }

        public void OnPostprocessBuild(BuildReport report)
        {
            // Delete Files (check if they exist first)
            Debug.Log("postprocess");
        }
3 Likes

@Xarbrough_1 summarized the problem really well.

BuildPlayerWindow.RegisterBuildPlayerHandler is not a solution for everybody, since the build process can be launched in many ways…

What we really need is an interface that gets called no matter what the build result is. It could have the same signature as OnPostProcessBuild, the supplied BuildReport could be inspected to determine the build status, dropping all work if BuildReport.summary.result != BuildResult.Succeeded (this can even be suggested in the docs and in the supplied example)

From an architectural PoV, this would not be a breaking change, all existing scripts would continue to work (as best as they can for now, anyway) and future scripts could leverage this new interface.

Would this allow package maintainers to write bad code? Well, maybe. But had they decided to do the same as @SG_roboryantron suggested (nice hack, btw!), it would be even worse

2 Likes

@Ryan-Unity Then how should I know when to remove scripting symbols from the editor?

I use scripting symbols for automatize building process and set things to create different kind of builds. And then I want to remove the symbols to be able to use the editor correctly after that. Now I need to remove from the editor ~10 symbol after I press Cancel during building, because I realized I didn’t set something. And I create 4 builds at once.

Maybe it doesn’t make sense to you to develop delegates for build cancellation, but it makes sense to others for sure.

Another vote here for making this callback function after a build failure/cancellation. At a bare minimum, it would be nice if the documentation actually informed us that the callback is not raised in these circumstances. To me, it currently implies the opposite and has cost me some wasted development time today.

2 Likes

I hear you, but a simple delegate to hook onto should not really affect the time that it takes ti fail a build. Developers that are concerned about the CPU cycles can simply ignore the delegate and those who don’t care can use it.

Thanks for the hint. This can be used as a workaround to manually build said functionality. Just be aware, that using this from multiple spots will override previous registrations (there’s at least a warning for this case, which saved me some trouble, when realizing the registration was done by an other script already).
To work around that and the missing cancel delegate, I transformed the suggestion into a simple script, to be found here: BuildHandler.cs
This example also shows, that there’s hardly any overhead, if the OnBuildCleanup event isn’t subscribed to.

1 Like

I would also like to have a callback for build failure.

I do code generation in OnPreprocessBuild, and remove it in OnPostprocessBuild. If build fails, all generated code remains in the project and can be accidentally pushed into VCS.

Bump, just to support the request of getting this implementation changed or extended. At least mention it in the docs: Unity - Scripting API: IPostprocessBuildWithReport

BuildReport having an Error and Success result threw me off too. I had to Google just to land here. It’s not a good experience. I have sent a feedback comment on the documentation page about it, hope they read it :wink:

Ping: I haven’t forgotten about all this good feedback. Very much do appreciate all the great information and details folks have provided. Sadly I don’t have anything I can mention yet.

3 Likes

Another vote to get this in.

It is unbelievable that in September 2023 this is still an issue: OnPostProcessBuild(BuildReport report) is still called when report.summary.result is Uknown and there are, as far as I know, no other way to have a callback when the actual build has been completed.

Unity version 220.3.9f1.

Seeing the previous post, I really don’t think it is a bug but if it is, please, let me know and I will submit a bug report.

This has worked for me, though it really is a workaround that should not be needed.

public void OnPreprocessBuild(BuildReport report)
{
    // We have to do it this way because IPostprocessBuildWithReport is not fired if the build fails:
    // see: https://discussions.unity.com/t/791031
    waitForBuildCompletion(report);
}

async void waitForBuildCompletion(BuildReport report)
{
    while (BuildPipeline.isBuildingPlayer || report.summary.result == BuildResult.Unknown)
    {
        await Task.Delay(1000);
    }

    OnPostprocessBuild(report);
}

public void OnPostprocessBuild(BuildReport report)
{
    // Do your stuff after the build.
}
1 Like