// ObjectListNode.cpp
//------------------------------------------------------------------------------

// Includes
//------------------------------------------------------------------------------
#include "ObjectListNode.h"

// FBuildCore
#include "Tools/FBuild/FBuildCore/BFF/BFFVariable.h"
#include "Tools/FBuild/FBuildCore/BFF/Functions/Function.h"
#include "Tools/FBuild/FBuildCore/BFF/Functions/FunctionObjectList.h"
#include "Tools/FBuild/FBuildCore/FBuild.h"
#include "Tools/FBuild/FBuildCore/FLog.h"
#include "Tools/FBuild/FBuildCore/Graph/CompilerInfoNode.h"
#include "Tools/FBuild/FBuildCore/Graph/CompilerNode.h"
#include "Tools/FBuild/FBuildCore/Graph/DirectoryListNode.h"
#include "Tools/FBuild/FBuildCore/Graph/LibraryNode.h"
#include "Tools/FBuild/FBuildCore/Graph/NodeGraph.h"
#include "Tools/FBuild/FBuildCore/Graph/ObjectNode.h"
#include "Tools/FBuild/FBuildCore/Graph/UnityNode.h"
#include "Tools/FBuild/FBuildCore/Helpers/Args.h"

// Core
#include "Core/FileIO/IOStream.h"
#include "Core/FileIO/PathUtils.h"
#include "Core/Math/xxHash.h"
#include "Core/Strings/AStackString.h"

// Reflection
//------------------------------------------------------------------------------
REFLECT_NODE_BEGIN( ObjectListNode, Node )
    REFLECT( m_Compiler, MetaFile() + MetaAllowNonFile() + MetaRequired() )
    REFLECT( m_CompilerOptions, MetaRequired() )
    REFLECT( m_CompilerOptionsDeoptimized )
    REFLECT( m_CompilerOutputPath, MetaPath() )
    REFLECT( m_CompilerOutputPrefix )
    REFLECT( m_CompilerOutputExtension )
    REFLECT( m_CompilerOutputKeepBaseExtension )
    REFLECT( m_CompilerInputAllowNoFiles )
    REFLECT( m_CompilerInputPath, MetaPath() )
    REFLECT( m_CompilerInputPattern )
    REFLECT( m_CompilerInputPathRecurse )
    REFLECT( m_CompilerInputExcludePath, MetaPath() )
    REFLECT( m_CompilerInputExcludedFiles, MetaFile( true ) )
    REFLECT( m_CompilerInputExcludePattern, MetaFile( true ) )
    REFLECT( m_CompilerInputUnity )
    REFLECT( m_CompilerInputFiles, MetaFile() )
    REFLECT( m_CompilerInputFilesRoot, MetaPath() )
    REFLECT( m_CompilerInputObjectLists )
    REFLECT( m_CompilerForceUsing, MetaFile() )
    REFLECT( m_DeoptimizeWritableFiles )
    REFLECT( m_DeoptimizeWritableFilesWithToken )
    REFLECT( m_AllowDistribution )
    REFLECT( m_AllowCaching )
    REFLECT( m_Hidden )
    // Precompiled Headers
    REFLECT( m_PCHInputFile, MetaFile() )
    REFLECT( m_PCHOutputFile, MetaFile() )
    REFLECT( m_PCHOptions )
    // Preprocessor
    REFLECT( m_Preprocessor, MetaFile() + MetaAllowNonFile() )
    REFLECT( m_PreprocessorOptions )
    REFLECT_RENAME( m_PreBuildDependencyNames, "PreBuildDependencies", MetaFile() + MetaAllowNonFile() )
    REFLECT( m_ConcurrencyGroupName )

    // Internal State
    REFLECT( m_CompilerNode, MetaHidden() )
    REFLECT( m_PreprocessorNode, MetaHidden() )
    REFLECT( m_CompilerInfoNode, MetaHidden() )
    REFLECT( m_PrecompiledHeaderName, MetaHidden() )
#if defined( __WINDOWS__ )
    REFLECT( m_PrecompiledHeaderCPPFile, MetaHidden() )
#endif
    REFLECT( m_PCHObjectFileName, MetaHidden() )
    REFLECT( m_ExtraPDBPath, MetaHidden() )
    REFLECT( m_ExtraASMPath, MetaHidden() )
    REFLECT( m_ExtraSourceDependenciesPath, MetaHidden() )
    REFLECT( m_ObjectListInputStartIndex, MetaHidden() )
    REFLECT( m_ObjectListInputEndIndex, MetaHidden() )
    REFLECT( m_OwnerObjectListHash, MetaHidden() )
    REFLECT_RENAME( m_CompilerFlags.m_Flags, "ObjFlags", MetaHidden() )
    REFLECT_RENAME( m_PreprocessorFlags.m_Flags, "ObjFlagsPreprocessor", MetaHidden() )
    REFLECT( m_ConcurrencyGroupIndex, MetaHidden() )
REFLECT_END( ObjectListNode )

// ObjectListNode
//------------------------------------------------------------------------------
ObjectListNode::ObjectListNode()
    : Node( Node::OBJECT_LIST_NODE )
{
    m_LastBuildTimeMs = 10000;

    m_CompilerInputPattern.EmplaceBack( "*.cpp" );
}

// Initialize
//------------------------------------------------------------------------------
/*virtual*/ bool ObjectListNode::Initialize( NodeGraph & nodeGraph, const BFFToken * iter, const Function * function )
{
    // .PreBuildDependencies
    if ( !InitializePreBuildDependencies( nodeGraph, iter, function, m_PreBuildDependencyNames ) )
    {
        return false; // InitializePreBuildDependencies will have emitted an error
    }

    // .ConcurrencyGroupName
    if ( !InitializeConcurrencyGroup( nodeGraph,
                                      iter,
                                      function,
                                      m_ConcurrencyGroupName,
                                      m_ConcurrencyGroupIndex ) )
    {
        return false; // InitializeConcurrencyGroup will have emitted an error
    }

    // .Compiler
    if ( !Function::GetCompilerNode( nodeGraph, iter, function, m_Compiler, m_CompilerNode ) )
    {
        return false; // GetCompilerNode will have emitted an error
    }

    // Check for CSharp (this must use CSAssembly not ObjectList)
    if ( m_CompilerNode->GetCompilerFamily() == CompilerNode::CompilerFamily::CSHARP )
    {
        Error::Error_1503_CSharpCompilerShouldUseCSAssembly( iter, function );
        return false;
    }

    // .Preprocessor
    if ( m_Preprocessor.IsEmpty() == false )
    {
        // get the preprocessor executable
        if ( Function::GetCompilerNode( nodeGraph, iter, function, m_Preprocessor, m_PreprocessorNode ) == false )
        {
            return false; // GetCompilerNode will have emitted an error
        }
    }

    // .CompilerForceUsing
    // (ObjectListNode doesn't need to depend on this, but we want to check it so that
    //  we can raise errors during parsing instead of during the build when ObjectNode might be created)
    Dependencies compilerForceUsing;
    if ( !Function::GetFileNodes( nodeGraph, iter, function, m_CompilerForceUsing, ".CompilerForceUsing", compilerForceUsing ) )
    {
        return false; // GetFileNode will have emitted an error
    }

    // Check Deoptimized compiler options which are conditionally not optional
    if ( ( m_DeoptimizeWritableFiles || m_DeoptimizeWritableFilesWithToken ) && m_CompilerOptionsDeoptimized.IsEmpty() )
    {
        Error::Error_1101_MissingProperty( iter, function, AStackString( ".CompilerOptionsDeoptimized" ) );
        return false;
    }

    CalculateOwnerObjectListHash();

    // Creating a PCH?
    const bool creatingPCH = ( m_PCHInputFile.IsEmpty() == false );
    ObjectNode * precompiledHeader = nullptr;
    if ( creatingPCH )
    {
        // .PCHOptions are required to create PCH
        if ( m_PCHOutputFile.IsEmpty() || m_PCHOptions.IsEmpty() )
        {
            Error::Error_1300_MissingPCHArgs( iter, function );
            return false;
        }

        // Check PCH creation command line options
        const ObjectNode::CompilerFlags pchFlags = ObjectNode::DetermineFlags( m_CompilerNode, m_PCHOptions, true, false );
        if ( pchFlags.IsMSVC() || pchFlags.IsClangCl() )
        {
            if ( ( (FunctionObjectList *)function )->CheckMSVCPCHFlags_Create( iter, m_PCHOptions, m_PCHOutputFile, GetObjExtension(), m_PCHObjectFileName ) == false )
            {
                return false; // CheckMSVCPCHFlags_Create will have emitted an error
            }
        }

        // PCH can be shared between ObjectLists, but must only be defined once
        if ( nodeGraph.FindNode( m_PCHOutputFile ) )
        {
            Error::Error_1301_AlreadyDefinedPCH( iter, function, m_PCHOutputFile.Get() );
            return false;
        }

        // Create the PCH node
        precompiledHeader = CreateObjectNode( nodeGraph,
                                              iter,
                                              function,
                                              pchFlags,
                                              ObjectNode::CompilerFlags(),
                                              m_PCHOutputFile,
                                              m_PCHInputFile );
        if ( precompiledHeader == nullptr )
        {
            return false; // CreateObjectNode will have emitted an error
        }
    }

    // Are we compiling and files?
    const bool compilingFiles = ( ( m_CompilerInputPath.IsEmpty() == false ) ||
                                  ( m_CompilerInputFiles.IsEmpty() == false ) ||
                                  ( m_CompilerInputUnity.IsEmpty() == false ) ||
                                  ( m_CompilerInputObjectLists.IsEmpty() == false ) );
    if ( compilingFiles )
    {
        // Using a PCH?
        const bool usingPCH = ( m_PCHOutputFile.IsEmpty() == false );

        // Cache flags for compiler and preprocessor
        m_CompilerFlags = ObjectNode::DetermineFlags( m_CompilerNode, m_CompilerOptions, false, usingPCH );
        if ( m_PreprocessorNode )
        {
            m_PreprocessorFlags = ObjectNode::DetermineFlags( m_PreprocessorNode, m_PreprocessorOptions, false, usingPCH );
        }

        // Check validity of PCH setup
        if ( usingPCH )
        {
            // Check for correct PCH usage options
            if ( m_CompilerFlags.IsMSVC() || m_CompilerFlags.IsClangCl() )
            {
                if ( ( (FunctionObjectList *)function )->CheckMSVCPCHFlags_Use( iter, m_CompilerOptions, m_CompilerFlags ) == false )
                {
                    return false; // CheckMSVCPCHFlags_Use will have emitted an error
                }
            }

            // Get the PCH node
            if ( creatingPCH )
            {
                ASSERT( precompiledHeader ); // This ObjectList also should have created it above
            }
            else
            {
                // If we are not creating it, we must be re-using it from another object list
                const Node * node = nodeGraph.FindNode( m_PCHOutputFile );
                if ( ( node == nullptr ) ||
                     ( node->GetType() != Node::OBJECT_NODE ) ||
                     ( node->CastTo<ObjectNode>()->GetCompilerFlags().IsCreatingPCH() == false ) )
                {
                    // PCH was not defined
                    Error::Error_1104_TargetNotDefined( iter, function, "PCHOutputFile", m_PCHOutputFile );
                    return false;
                }
                precompiledHeader = node->CastTo<ObjectNode>();
            }
        }

        // .CompilerOptions
        if ( ( (FunctionObjectList *)function )->CheckCompilerOptions( iter, m_CompilerOptions, m_CompilerFlags ) == false )
        {
            return false; // CheckCompilerOptions will have emitted an error
        }

        // .CompilerOutputPath is required when compiling files (not needed if only creating a PCH)
        if ( m_CompilerOutputPath.IsEmpty() )
        {
            Error::Error_1101_MissingProperty( iter, function, AStackString( "CompilerOutputPath" ) );
            return false;
        }
    }

    // Cache precompiled header details
    if ( precompiledHeader )
    {
        m_PrecompiledHeaderName = precompiledHeader->GetName();

        // Cache the CPP file associated with the PCH for MSVC (and Clang in MSVC mode)
        // so that it can be excluded from normal compilation
#if defined( __WINDOWS__ )
        if ( ( m_CompilerNode->GetCompilerFamily() == CompilerNode::CompilerFamily::MSVC ) ||
             ( m_CompilerNode->GetCompilerFamily() == CompilerNode::CompilerFamily::CLANG_CL ) )
        {
            m_PrecompiledHeaderCPPFile = precompiledHeader->GetPrecompiledHeaderCPPFile()->GetName();
        }
#endif
    }

    // .CompilerInputUnity
    Dependencies compilerInputUnity;
    for ( const AString & unity : m_CompilerInputUnity )
    {
        Node * n = nodeGraph.FindNode( unity );
        if ( n == nullptr )
        {
            Error::Error_1104_TargetNotDefined( iter, function, "CompilerInputUnity", unity );
            return false;
        }
        if ( n->GetType() != Node::UNITY_NODE )
        {
            // TODO:B We should support aliases
            Error::Error_1102_UnexpectedType( iter, function, "CompilerInputUnity", unity, n->GetType(), Node::UNITY_NODE );
            return false;
        }
        compilerInputUnity.Add( n );
    }

    // .CompilerInputPath
    Dependencies compilerInputPath;
    if ( !Function::GetDirectoryListNodeList( nodeGraph,
                                              iter,
                                              function,
                                              m_CompilerInputPath,
                                              m_CompilerInputExcludePath,
                                              m_CompilerInputExcludedFiles,
                                              m_CompilerInputExcludePattern,
                                              m_CompilerInputPathRecurse,
                                              false, // Don't include read-only status in hash
                                              false, // Don't include directories
                                              &m_CompilerInputPattern,
                                              "CompilerInputPath",
                                              compilerInputPath ) )
    {
        return false; // GetDirectoryListNodeList will have emitted an error
    }
    ASSERT( compilerInputPath.GetSize() == m_CompilerInputPath.GetSize() ); // No need to store count since they should be the same

    // .CompilerInputFiles
    // NOTE: We get the file nodes to ensure nice errors are reported for undefined
    // targets during bff parsing, but we don't depend on these files (the object nodes
    // we create later depend on these files)
    {
        Dependencies compilerInputFiles;
        if ( !Function::GetFileNodes( nodeGraph, iter, function, m_CompilerInputFiles, "CompilerInputFiles", compilerInputFiles ) )
        {
            return false; // GetFileNode will have emitted an error
        }
    }

    // .CompilerInputObjectLists
    Dependencies compilerInputObjectLists;
    if ( Function::GetObjectListNodes( nodeGraph, iter, function, m_CompilerInputObjectLists, "CompilerInputObjectLists", compilerInputObjectLists ) == false )
    {
        return false; //
    }

    // Extra output paths
    // Currently these are MSVC only
    if ( m_CompilerFlags.IsMSVC() || m_CompilerFlags.IsClangCl() )
    {
        // clang-format off
        ( (FunctionObjectList *)function )->GetExtraOutputPaths( m_CompilerOptions,
                                                                 m_ExtraPDBPath,
                                                                 m_ExtraASMPath,
                                                                 m_ExtraSourceDependenciesPath );
        // clang-format on
    }

    // Check LightCache compatibility
    if ( !CheckLightCacheArgs( nodeGraph,
                               iter,
                               function,
                               compilingFiles,
                               m_CompilerInfoNode ) )
    {
        return false;
    }

    // Store dependencies
    m_StaticDependencies.SetCapacity( ( m_CompilerInfoNode ? 1 : 0 ) +
                                      compilerInputPath.GetSize() +
                                      compilerInputUnity.GetSize() +
                                      compilerInputObjectLists.GetSize() );
    if ( m_CompilerInfoNode )
    {
        m_StaticDependencies.Add( m_CompilerInfoNode );
        m_ObjectListInputStartIndex++;
    }
    m_StaticDependencies.Add( compilerInputPath );
    m_StaticDependencies.Add( compilerInputUnity );
    m_StaticDependencies.Add( compilerInputObjectLists );

    // Take note of how many things are treated as inputs
    // (this is needed so LibraryNode can add some additional things)
    m_ObjectListInputEndIndex = (uint32_t)m_StaticDependencies.GetSize();

    return true;
}

// DESTRUCTOR
//------------------------------------------------------------------------------
ObjectListNode::~ObjectListNode() = default;

// IsAFile
//------------------------------------------------------------------------------
/*virtual*/ bool ObjectListNode::IsAFile() const
{
    return false;
}

//------------------------------------------------------------------------------
/*virtual*/ uint8_t ObjectListNode::GetConcurrencyGroupIndex() const
{
    return m_ConcurrencyGroupIndex;
}

// GatherDynamicDependencies
//------------------------------------------------------------------------------
/*virtual*/ bool ObjectListNode::GatherDynamicDependencies( NodeGraph & nodeGraph )
{
    // clear dynamic deps from previous passes
    m_DynamicDependencies.Clear();

    // Handle converting all static inputs into dynamic ones (i.e. cpp->obj)
    for ( size_t i = m_ObjectListInputStartIndex; i < m_ObjectListInputEndIndex; ++i )
    {
        const Dependency & dep = m_StaticDependencies[ i ];

        // is this a dir list?
        if ( dep.GetNode()->GetType() == Node::DIRECTORY_LIST_NODE )
        {
            // get the list of files
            const DirectoryListNode * dln = dep.GetNode()->CastTo<DirectoryListNode>();
            const Array<FileIO::FileInfo> & files = dln->GetFiles();
            m_DynamicDependencies.SetCapacity( m_DynamicDependencies.GetSize() + files.GetSize() );
            for ( const FileIO::FileInfo & file : files )
            {
                // Create the file node (or find an existing one)
                Node * n = nodeGraph.FindNode( file.m_Name );
                if ( n == nullptr )
                {
                    n = nodeGraph.CreateNode<FileNode>( file.m_Name );
                }
                else if ( n->IsAFile() == false )
                {
                    FLOG_ERROR( "Library() .CompilerInputFile '%s' is not a FileNode (type: %s)", n->GetName().Get(), n->GetTypeName() );
                    return false;
                }

                // ignore the precompiled header as a convenience for the user
                // so they don't have to exclude it explicitly
#if defined( __WINDOWS__ )
                if ( n->GetName() == m_PrecompiledHeaderCPPFile )
                {
                    continue;
                }
#endif

                // create the object that will compile the above file
                if ( CreateDynamicObjectNode( nodeGraph, n->GetName(), dln->GetPath() ) == false )
                {
                    return false; // CreateDynamicObjectNode will have emitted error
                }
            }
        }
        else if ( dep.GetNode()->GetType() == Node::UNITY_NODE )
        {
            // get the dir list from the unity node
            const UnityNode * un = dep.GetNode()->CastTo<UnityNode>();

            // unity files
            for ( const AString & unityFile : un->GetUnityFileNames() )
            {
                Node * n = nodeGraph.FindNode( unityFile );
                if ( n == nullptr )
                {
                    n = nodeGraph.CreateNode<FileNode>( unityFile );
                }
                else if ( n->IsAFile() == false )
                {
                    FLOG_ERROR( "Library() .CompilerInputUnity '%s' is not a FileNode (type: %s)", n->GetName().Get(), n->GetTypeName() );
                    return false;
                }

                // create the object that will compile the above file
                if ( CreateDynamicObjectNode( nodeGraph, n->GetName(), AString::GetEmpty(), true ) == false )
                {
                    return false; // CreateDynamicObjectNode will have emitted error
                }
            }

            // files from unity to build individually
            for ( const UnityIsolatedFile & isolatedFile : un->GetIsolatedFileNames() )
            {
                Node * n = nodeGraph.FindNode( isolatedFile.GetFileName() );
                if ( n == nullptr )
                {
                    n = nodeGraph.CreateNode<FileNode>( isolatedFile.GetFileName() );
                }
                else if ( n->IsAFile() == false )
                {
                    FLOG_ERROR( "Library() Isolated '%s' is not a FileNode (type: %s)", n->GetName().Get(), n->GetTypeName() );
                    return false;
                }

                // create the object that will compile the above file
                if ( CreateDynamicObjectNode( nodeGraph, n->GetName(), isolatedFile.GetDirListOriginPath(), false, true ) == false )
                {
                    return false; // CreateDynamicObjectNode will have emitted error
                }
            }
        }
        else if ( dep.GetNode()->GetType() == Node::OBJECT_LIST_NODE )
        {
            // get the list of files from the ObjectListNode
            const ObjectListNode * objListNode = dep.GetNode()->CastTo<ObjectListNode>();
            Array<AString> objListFiles;
            objListNode->GetInputFiles( objListFiles );

            // iterate all the files in the object list
            for ( const AString & file : objListFiles )
            {
                const Node * n = nodeGraph.FindNode( file );
                if ( n == nullptr )
                {
                    FLOG_ERROR( "ObjectListNode: Missing Node '%s'", file.Get() );
                    return false;
                }
                else if ( n->IsAFile() == false )
                {
                    FLOG_ERROR( "ObjectListNode: '%s' is not a FileNode (type: %s)", n->GetName().Get(), n->GetTypeName() );
                    return false;
                }

                // create the object that will compile the above file
                if ( CreateDynamicObjectNode( nodeGraph, n->GetName(), objListNode->GetCompilerOutputPath() ) == false )
                {
                    return false; // CreateDynamicObjectNode will have emitted error
                }
            }
        }
        else if ( dep.GetNode()->IsAFile() )
        {
            // a single file, create the object that will compile it
            if ( CreateDynamicObjectNode( nodeGraph, dep.GetNode()->GetName(), AString::GetEmpty() ) == false )
            {
                return false; // CreateDynamicObjectNode will have emitted error
            }
        }
        else
        {
            ASSERT( false ); // unexpected node type
        }
    }

    // Depend on objects for loose files
    for ( const AString & file : m_CompilerInputFiles )
    {
        if ( CreateDynamicObjectNode( nodeGraph, file, AString::GetEmpty() ) == false )
        {
            return false; // CreateDynamicObjectNode will have emitted error
        }
    }

    // If we have a precompiled header, add that to our dynamic deps so that
    // any symbols in the PCH's .obj are also linked, when either:
    // a) we are a static library
    // b) a DLL or executable links our .obj files
    if ( m_PrecompiledHeaderName.IsEmpty() == false )
    {
        Node * node = nodeGraph.FindNode( m_PrecompiledHeaderName );
        ASSERT( node ); // Should always exist if we get here
        m_DynamicDependencies.Add( node );
    }

    return true;
}

// DoDynamicDependencies
//------------------------------------------------------------------------------
/*virtual*/ bool ObjectListNode::DoDynamicDependencies( NodeGraph & nodeGraph )
{
    if ( GatherDynamicDependencies( nodeGraph ) == false )
    {
        return false; // GatherDynamicDependencies will have emitted error
    }

    // make sure we have something to build!
    if ( ( m_DynamicDependencies.GetSize() == 0 ) && ( m_CompilerInputAllowNoFiles == false ) )
    {
        FLOG_ERROR( "No files found to build '%s'", GetName().Get() );
        return false;
    }

    if ( m_ExtraASMPath.IsEmpty() == false )
    {
        if ( !FileIO::EnsurePathExists( m_ExtraASMPath ) )
        {
            FLOG_ERROR( "Failed to create folder for .asm file '%s'", m_ExtraASMPath.Get() );
            return false;
        }
    }

    if ( m_ExtraPDBPath.IsEmpty() == false )
    {
        if ( !FileIO::EnsurePathExists( m_ExtraPDBPath ) )
        {
            FLOG_ERROR( "Failed to create folder for .pdb file '%s'", m_ExtraPDBPath.Get() );
            return false;
        }
    }

    if ( m_ExtraSourceDependenciesPath.IsEmpty() == false )
    {
        if ( !FileIO::EnsurePathExists( m_ExtraSourceDependenciesPath ) )
        {
            FLOG_ERROR( "Failed to create folder for /sourceDependencies file '%s'", m_ExtraSourceDependenciesPath.Get() );
            return false;
        }
    }

    return true;
}

// DoBuild
//------------------------------------------------------------------------------
/*virtual*/ Node::BuildResult ObjectListNode::DoBuild( Job * /*job*/ )
{
    // Generate stamp
    if ( m_DynamicDependencies.IsEmpty() )
    {
        m_Stamp = 1; // Non-zero
    }
    else
    {
        StackArray<uint64_t> stamps;
        stamps.SetCapacity( m_DynamicDependencies.GetSize() );
        for ( const Dependency & dep : m_DynamicDependencies )
        {
            const ObjectNode * on = dep.GetNode()->CastTo<ObjectNode>();
            ASSERT( on->GetStamp() );
            stamps.Append( on->GetStamp() );
        }
        m_Stamp = xxHash3::Calc64( &stamps[ 0 ], ( stamps.GetSize() * sizeof( uint64_t ) ) );
    }

    return BuildResult::eOk;
}

// GetInputFiles
//------------------------------------------------------------------------------
void ObjectListNode::GetInputFiles( bool objectsInsteadOfLibs, Array<AString> & outInputs ) const
{
    for ( const Dependency & dep : m_DynamicDependencies )
    {
        const Node * n = dep.GetNode();

        // handle pch files - get path to object
        if ( n->GetType() == Node::OBJECT_NODE )
        {
            // handle pch files - get path to matching object
            const ObjectNode * on = n->CastTo<ObjectNode>();
            if ( on->IsCreatingPCH() )
            {
                if ( on->IsMSVC() || on->IsClangCl() )
                {
                    // PCH files should only be included once
                    if ( outInputs.Find( on->GetPCHObjectName() ) == nullptr )
                    {
                        outInputs.EmplaceBack( on->GetPCHObjectName() );
                    }
                }
                else
                {
                    // Clang/GCC/SNC don't have an object to link for a pch
                }
                continue;
            }
        }

        // extract objects from additional lists
        if ( n->GetType() == Node::OBJECT_LIST_NODE )
        {
            ASSERT( GetType() == Node::LIBRARY_NODE ); // should only be possible for a LibraryNode

            // insert all the objects in the object list
            const ObjectListNode * oln = n->CastTo<ObjectListNode>();
            oln->GetInputFiles( objectsInsteadOfLibs, outInputs );
            continue;
        }

        // get objects used to create libs
        if ( ( n->GetType() == Node::LIBRARY_NODE ) && objectsInsteadOfLibs )
        {
            ASSERT( GetType() == Node::LIBRARY_NODE ); // should only be possible for a LibraryNode

            // insert all the objects in the object list
            const LibraryNode * ln = n->CastTo<LibraryNode>();
            ln->GetInputFiles( objectsInsteadOfLibs, outInputs );
            continue;
        }

        // normal object
        outInputs.EmplaceBack( n->GetName() );
    }
}

// GetInputFiles
//------------------------------------------------------------------------------
void ObjectListNode::GetInputFiles( Array<AString> & files ) const
{
    // only valid to call on ObjectListNode (not LibraryNode)
    ASSERT( GetType() == Node::OBJECT_LIST_NODE );

    files.SetCapacity( files.GetCapacity() + m_DynamicDependencies.GetSize() );
    for ( const Dependency & dep : m_DynamicDependencies )
    {
        files.Append( dep.GetNode()->GetName() );
    }
}

// GetObjectFileName
//------------------------------------------------------------------------------
void ObjectListNode::GetObjectFileName( const AString & fileName, const AString & baseDir, AString & objFile )
{
    // Transform src file to dst object path
    // get file name only (no path, no ext)
    const char * lastSlash = fileName.FindLast( NATIVE_SLASH );
    lastSlash = lastSlash ? ( lastSlash + 1 ) : fileName.Get();
    const char * lastDot = m_CompilerOutputKeepBaseExtension ? fileName.GetEnd() : fileName.FindLast( '.' );
    lastDot = lastDot && ( lastDot > lastSlash ) ? lastDot : fileName.GetEnd();

    // if source comes from a directory listing, use path relative to dirlist base
    // to replicate the folder hierarchy in the output
    AStackString subPath;
    if ( baseDir.IsEmpty() == false )
    {
        ASSERT( NodeGraph::IsCleanPath( baseDir ) );
        if ( PathUtils::PathBeginsWith( fileName, baseDir ) )
        {
            // ... use everything after that
            subPath.Assign( fileName.Get() + baseDir.GetLength(), lastSlash ); // includes last slash
        }
    }
    else
    {
        if ( !m_CompilerInputFilesRoot.IsEmpty() && PathUtils::PathBeginsWith( fileName, m_CompilerInputFilesRoot ) )
        {
            // ... use everything after that
            subPath.Assign( fileName.Get() + m_CompilerInputFilesRoot.GetLength(), lastSlash ); // includes last slash
        }
    }

    const AStackString fileNameOnly( lastSlash, lastDot );
    objFile = m_CompilerOutputPath;
    objFile += subPath;
    objFile += m_CompilerOutputPrefix;
    objFile += fileNameOnly;
    objFile += GetObjExtension();
}

//------------------------------------------------------------------------------
void ObjectListNode::CalculateOwnerObjectListHash()
{
    ASSERT( m_OwnerObjectListHash == 0 ); // Should only be called once

    xxHash3Accumulator accumulator;
    for ( const AString & string : GetCompilerForceUsing() )
    {
        accumulator.AddData( string );
    }
    accumulator.AddData( GetCompilerOptions() );
    accumulator.AddData( GetCompilerOptionsDeoptimized() );
    accumulator.AddData( GetCompilerOptionsPCH() );
    accumulator.AddData( GetPCHObjectFileName() );
    accumulator.AddData( GetPrecompiledHeaderName() );
    const uint32_t flags = ( GetDeoptimizeWritableFiles() ? 1u : 0u ) |
                           ( GetDeoptimizeWritableFilesWithToken() ? 2u : 0u ) |
                           ( IsCachingAllowed() ? 4u : 0u ) |
                           ( IsDistributionAllowed() ? 8u : 0u );
    accumulator.AddData( &flags, sizeof( flags ) );
    m_OwnerObjectListHash = accumulator.Finalize32();
}

// CreateDynamicObjectNode
//------------------------------------------------------------------------------
bool ObjectListNode::CreateDynamicObjectNode( NodeGraph & nodeGraph,
                                              const AString & inputFileName,
                                              const AString & baseDir,
                                              bool isUnityNode,
                                              bool isIsolatedFromUnityNode )
{
    AStackString objFile;
    GetObjectFileName( inputFileName, baseDir, objFile );

    // Create an ObjectNode to compile the above file
    // and depend on that
    Node * on = nodeGraph.FindNode( objFile );
    if ( on == nullptr )
    {
        // Handle Unity modification of flags
        ObjectNode::CompilerFlags flags = m_CompilerFlags;
        if ( isUnityNode )
        {
            flags.Set( ObjectNode::CompilerFlags::FLAG_UNITY );
        }
        if ( isIsolatedFromUnityNode )
        {
            flags.Set( ObjectNode::CompilerFlags::FLAG_ISOLATED_FROM_UNITY );
        }

        const BFFToken * token = nullptr;
        ObjectNode * objectNode = CreateObjectNode( nodeGraph, token, nullptr, flags, m_PreprocessorFlags, objFile, inputFileName );
        if ( !objectNode )
        {
            FLOG_ERROR( "Failed to create node '%s'!", objFile.Get() );
            return false;
        }
        on = objectNode;
    }
    else if ( on->GetType() != Node::OBJECT_NODE )
    {
        FLOG_ERROR( "Node '%s' is not an ObjectNode (type: %s)", on->GetName().Get(), on->GetTypeName() );
        return false;
    }
    else
    {
        const ObjectNode * other = on->CastTo<ObjectNode>();

        // Check for conflicts
        const bool conflict = ( inputFileName != other->GetSourceFile()->GetName() ) ||
                              ( this != &other->GetOwnerObjectList() );
        if ( conflict )
        {
            FLOG_ERROR( "Conflicting objects found for: %s\n"
                        " Source A  : %s\n"
                        " ObjectList: %s\n"
                        "AND\n"
                        " Source B  : %s\n"
                        " ObjectList: %s\n",
                        objFile.Get(),
                        inputFileName.Get(),
                        m_Name.Get(),
                        other->GetSourceFile()->GetName().Get(),
                        other->GetOwnerObjectList().GetName().Get() );
            return false;
        }
    }
    m_DynamicDependencies.Add( on );
    return true;
}

// CreateObjectNode
//------------------------------------------------------------------------------
ObjectNode * ObjectListNode::CreateObjectNode( NodeGraph & nodeGraph,
                                               const BFFToken * iter,
                                               const Function * function,
                                               const ObjectNode::CompilerFlags flags,
                                               const ObjectNode::CompilerFlags preprocessorFlags,
                                               const AString & objectName,
                                               const AString & objectInput )
{
    ASSERT( m_OwnerObjectListHash );

    ObjectNode * node = nodeGraph.CreateNode<ObjectNode>( objectName, iter );
    node->m_CompilerInputFile = objectInput;
    node->m_CompilerFlags = flags;
    node->m_PreprocessorFlags = preprocessorFlags;
    node->m_OwnerObjectList = this;
    node->m_OwnerObjectListHash = m_OwnerObjectListHash;

    if ( !node->Initialize( nodeGraph, iter, function ) )
    {
        // TODO:A We have a node in the graph which is in an invalid state
        return nullptr; // Initialize will have emitted an error
    }
    return node;
}

// GetObjExtension
//------------------------------------------------------------------------------
const char * ObjectListNode::GetObjExtension() const
{
    if ( m_CompilerOutputExtension.IsEmpty() )
    {
#if defined( __WINDOWS__ )
        return ".obj";
#else
        return ".o";
#endif
    }
    return m_CompilerOutputExtension.Get();
}

// EnumerateInputFiles
//------------------------------------------------------------------------------
void ObjectListNode::EnumerateInputFiles( void ( *callback )( const AString & inputFile, const AString & baseDir, void * userData ), void * userData ) const
{
    // Statically specified files
    for ( const AString & file : m_CompilerInputFiles )
    {
        callback( file, AString::GetEmpty(), userData );
    }

    // Dynamically discovered files
    for ( size_t i = m_ObjectListInputStartIndex; i < m_ObjectListInputEndIndex; ++i )
    {
        const Node * node = m_StaticDependencies[ i ].GetNode();

        if ( node->GetType() == Node::DIRECTORY_LIST_NODE )
        {
            const DirectoryListNode * dln = node->CastTo<DirectoryListNode>();

            const Array<FileIO::FileInfo> & files = dln->GetFiles();
            for ( const FileIO::FileInfo & fi : files )
            {
                callback( fi.m_Name, dln->GetPath(), userData );
            }
        }
        else if ( node->GetType() == Node::UNITY_NODE )
        {
            const UnityNode * un = node->CastTo<UnityNode>();

            un->EnumerateInputFiles( callback, userData );
        }
        else if ( node->IsAFile() )
        {
            callback( node->GetName(), AString::GetEmpty(), userData );
        }
        else
        {
            ASSERT( false ); // unexpected node type
        }
    }
}

//------------------------------------------------------------------------------
bool ObjectListNode::CheckLightCacheArgs( NodeGraph & nodeGraph,
                                          const BFFToken * iter,
                                          const Function * function,
                                          bool compilingFiles,
                                          CompilerInfoNode *& outCompilerInfoDependency ) const
{
    outCompilerInfoDependency = nullptr;

    // If we're not compiling any files (for example in a Library()) then we
    // don't care about compiler compatibility as the compiler is unused
    if ( compilingFiles == false )
    {
        return true;
    }

    CompilerNode * compiler = m_PreprocessorNode ? m_PreprocessorNode : m_CompilerNode;
    if ( compiler->GetUseLightCache() == false )
    {
        return true; // Not using the LightCache, so there can be no problems
    }

    const ObjectNode::CompilerFlags & flags = m_PreprocessorNode ? m_PreprocessorFlags
                                                                 : m_CompilerFlags;

    // MSVC?
    if ( flags.IsMSVC() )
    {
        return true; // MSVC can always be used
    }

    // Clang?
    if ( flags.IsClang() || flags.IsClangCl() || flags.IsGCC() )
    {
        AStackString ciNodeName;
        ciNodeName.Format( "%s_$Info_%c%c$",
                           compiler->GetName().Get(),
                           flags.IsNoStdInc() ? '1' : '0',
                           flags.IsNoStdIncPP() ? '1' : '0' );
        CompilerInfoNode * ciNode = nullptr;
        if ( const Node * node = nodeGraph.FindNode( ciNodeName ) )
        {
            ciNode = node->CastTo<CompilerInfoNode>();
        }
        else
        {
            ciNode = nodeGraph.CreateNode( Node::COMPILER_INFO_NODE,
                                           ciNodeName,
                                           iter )
                         ->CastTo<CompilerInfoNode>();
            ciNode->m_Compiler = compiler;
            ciNode->m_NoStdInc = flags.IsNoStdInc();
            ciNode->m_NoStdIncPP = flags.IsNoStdIncPP();
            VERIFY( ciNode->Initialize( nodeGraph, iter, function ) ); // Failure should be impossible
        }
        outCompilerInfoDependency = ciNode;
        return true;
    }

    // Unsupported Compiler
    Error::Error_1502_LightCacheIncompatibleWithCompiler( iter, function );
    return false;
}

//------------------------------------------------------------------------------
