Is there a "PreProcessBuildAttribute" like "PostProcessBuildAttribute"??

…hm…

No, it’s not available at the time of writing.

Nope, but you can just programmatically build your project using the BuildPipeline:

Then through whatever means you want, you can trigger this (menu entry, button in a gui window, whatever). And you’re hook to before is just whatever lines you insert before calling BuildPlayer.

I do it as a ScriptableObject asset, and generalized a lot of the build process so I can create configurations and save them as assets:

I can even inherit from it to make project specific tweaks, like this one:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

using com.spacepuppy;
using com.spacepuppy.Collections;
using com.spacepuppy.Project;
using com.spacepuppyeditor;

using com.mansion;

namespace com.mansioneditor
{

    [CreateAssetMenu(fileName = "EpisodeBuildSettings", menuName = "Spacepuppy Build Pipeline/Episode Build Settings")]
    public class EpisodeBuildSettings : BuildSettings
    {

        #region Fields

        [SerializeField]
        private DistributionPlatform _distributionPlatform;
        [SerializeField]
        private EpisodeSettings _episodeSettings;
        [SerializeField]
        private string _resourcesFolderName;

        #endregion

        #region Properties

        public DistributionPlatform DistributionPlatform
        {
            get { return _distributionPlatform; }
            set { _distributionPlatform = value; }
        }

        public EpisodeSettings EpisodeSettings
        {
            get { return _episodeSettings; }
            set { _episodeSettings = value; }
        }

        public string ResourcesFolderName
        {
            get { return _resourcesFolderName; }
            set { _resourcesFolderName = value; }
        }

        #endregion

        #region Methods

        public override string[] GetScenePaths()
        {
            using (var lst = TempCollection.GetList<string>())
            {
                if (this.BootScene != null) lst.Add(AssetDatabase.GetAssetPath(this.BootScene));

                foreach (var scene in this.Scenes)
                {
                    lst.Add(AssetDatabase.GetAssetPath(scene));
                }

                if (this.EpisodeSettings != null && this.EpisodeSettings.TitleScreen.SceneAsset != null)
                {
                    var path = AssetDatabase.GetAssetPath(this.EpisodeSettings.TitleScreen.SceneAsset);
                    //if (lst.Contains(path)) lst.Remove(path);
                    //lst.Insert(1, path);
                    if (!lst.Contains(path))
                        lst.Add(path);
                }

                return lst.ToArray();
            }
        }

    

        #endregion

    }

    [CustomEditor(typeof(EpisodeBuildSettings))]
    public class EpisodeBuildSettingsEditor : BuildSettingsEditor
    {

        public const string PROP_DISTPLATFORM = "_distributionPlatform";
        public const string PROP_EPISODESETTINGS = "_episodeSettings";
        public const string PROP_RESOURCESFOLDERNAME = "_resourcesFolderName";

        public override void DrawBuildOptions()
        {
            base.DrawBuildOptions();

            this.DrawPropertyField(PROP_DISTPLATFORM);
            this.DrawPropertyField(PROP_EPISODESETTINGS);
            this.DrawPropertyField(PROP_RESOURCESFOLDERNAME);
        }



        public override void SyncToGlobalBuild()
        {
            var settings = this.target as EpisodeBuildSettings;
            var gameSettings = AssetDatabase.LoadAssetAtPath<Game>("Assets/Resources/GameSettings.asset");
            if (gameSettings != null)
            {
                Undo.RecordObject(gameSettings, "Sync Build Settings to GameSettings");
                gameSettings.SetEpisodeSettings(settings.EpisodeSettings);
            }

            base.SyncToGlobalBuild();
        }


        protected override IEnumerator DoBuild(BuildSettings.PostBuildOption postBuildOption)
        {
            var settings = this.target as EpisodeBuildSettings;
        
            if(!string.IsNullOrEmpty(settings.ResourcesFolderName))
            {
                var spath = @"Assets/Build/" + settings.ResourcesFolderName;
                AssetHelper.MoveFolder(spath, @"Assets/Build/Resources");
                AssetDatabase.Refresh();
            }
        
            var gameSettings = AssetDatabase.LoadAssetAtPath<Game>(@"Assets/Resources/GameSettings.asset");
            EpisodeSettings backupEpSettings = null;
            DistributionPlatform backupPlatform = DistributionPlatform.Generic;
            if (gameSettings != null)
            {
                backupEpSettings = gameSettings.GetEpisodeSettings();
                backupPlatform = gameSettings.Platform;

                gameSettings.SetEpisodeSettings(settings.EpisodeSettings);
                gameSettings.Platform = settings.DistributionPlatform;
            }

            try
            {
                settings.Build(postBuildOption);
            }
            finally
            {
                if (gameSettings != null)
                {
                    gameSettings.SetEpisodeSettings(backupEpSettings);
                    gameSettings.Platform = backupPlatform;
                }

                if (!string.IsNullOrEmpty(settings.ResourcesFolderName))
                {
                    AssetHelper.MoveFolder("Assets/Build/Resources", "Assets/Build/" + settings.ResourcesFolderName);
                    AssetDatabase.Refresh();
                }
            }
        
            yield break;
        }


    }

}

It visually looks like this:

I even have a ‘build build’ process to group builds together:

I use this to build multiple versions of the game and upload them in one click. Here is me building win/linux/macos and uploading for steam and itch.io:

As to your hook, if you wanted to use something similar to this.

You could insert a static event that gets trigger.

Or you could even use reflection, search through all the static methods out in the assembly, check for a custom attribute of your liking. And call that method. Completely emulating the PostProcessBuildAttribute.