Esempio n. 1
0
// DependOnNode
//------------------------------------------------------------------------------
/*static*/ bool LinkerNode::DependOnNode( NodeGraph & nodeGraph,
                                          const BFFIterator & iter,
                                          const Function * function,
                                          const AString & nodeName,
                                          Dependencies & nodes )
{
    // silently ignore empty nodes
    if ( nodeName.IsEmpty() )
    {
        return true;
    }

    Node * node = nodeGraph.FindNode( nodeName );

    // does it exist?
    if ( node != nullptr )
    {
        // process it
        return DependOnNode( iter, function, node, nodes );
    }

    // node not found - create a new FileNode, assuming we are
    // linking against an externally built library
    node = nodeGraph.CreateFileNode( nodeName );
    nodes.Append( Dependency( node ) );
    return true;
}
Esempio n. 2
0
// GetFileNode
//------------------------------------------------------------------------------
bool Function::GetFileNode( NodeGraph & nodeGraph, const BFFIterator & iter, Node * & fileNode, const char * name, bool required ) const
{
	// get the string containing the node name
	AStackString<> fileNodeName;
	if ( GetString( iter, fileNodeName, name, required ) == false )
	{
		return false;
	}

	// handle not-present
	if ( fileNodeName.IsEmpty() )
	{
		ASSERT( required == false ); // GetString should have managed required string
		fileNode = nullptr;
		return true;
	}

	// get/create the FileNode
	Node * n = nodeGraph.FindNode( fileNodeName );
	if ( n == nullptr )
	{
		n = nodeGraph.CreateFileNode( fileNodeName );
	}
	else if ( n->IsAFile() == false )
	{
		Error::Error_1103_NotAFile( iter, this, name, n->GetName(), n->GetType() );
		return false;
	}
	fileNode = n;
	return true;
}
Esempio n. 3
0
// GetOtherLibrary
//------------------------------------------------------------------------------
bool LinkerNode::GetOtherLibrary( NodeGraph & nodeGraph,
                                  const BFFIterator & iter,
                                  const Function * function,
                                  Dependencies & libs,
                                  const AString & path,
                                  const AString & lib,
                                  bool & found ) const
{
    found = false;

    AStackString<> potentialNodeName( path );
    if ( !potentialNodeName.IsEmpty() )
    {
        PathUtils::EnsureTrailingSlash( potentialNodeName );
    }
    potentialNodeName += lib;
    AStackString<> potentialNodeNameClean;
    NodeGraph::CleanPath( potentialNodeName, potentialNodeNameClean );

    // see if a node already exists
    Node * node = nodeGraph.FindNode( potentialNodeNameClean );
    if ( node )
    {
        // aliases not supported - must point to something that provides a file
        if ( node->IsAFile() == false )
        {
            Error::Error_1103_NotAFile( iter, function, ".LinkerOptions", potentialNodeNameClean, node->GetType() );
            return false;
        }

        // found existing node
        libs.Append( Dependency( node ) );
        found = true;
        return true; // no error
    }

    // see if the file exists on disk at this location
    if ( FileIO::FileExists( potentialNodeNameClean.Get() ) )
    {
        node = nodeGraph.CreateFileNode( potentialNodeNameClean );
        libs.Append( Dependency( node ) );
        found = true;
        FLOG_INFO( "Additional library '%s' assumed to be '%s'\n", lib.Get(), potentialNodeNameClean.Get() );
        return true; // no error
    }

    return true; // no error
}
Esempio n. 4
0
// GetFileNode
//------------------------------------------------------------------------------
bool Function::GetFileNode( NodeGraph & nodeGraph,
							const BFFIterator & iter,
                            const AString & file,
                            const char * inputVarName,
                            Dependencies & nodes ) const
{
	// get node for the dir we depend on
	Node * node = nodeGraph.FindNode( file );
	if ( node == nullptr )
	{
		node = nodeGraph.CreateFileNode( file );
    }
	else if ( node->IsAFile() == false )
	{
		Error::Error_1005_UnsupportedNodeType( iter, this, inputVarName, node->GetName(), node->GetType() );
		return false;
	}

	nodes.Append( Dependency( node ) );
	return true;
}
Esempio n. 5
0
// GatherDynamicDependencies
//------------------------------------------------------------------------------
/*virtual*/ bool ObjectListNode::GatherDynamicDependencies( NodeGraph & nodeGraph, bool forceClean )
{
    (void)forceClean; // dynamic deps are always re-added here, so this is meaningless

    // clear dynamic deps from previous passes
    m_DynamicDependencies.Clear();

#if defined( __WINDOWS__ )
    // On Windows, with MSVC we compile a cpp file to generate the PCH
    // Filter here to ensure that doesn't get compiled twice
    Node * pchCPP = nullptr;
    if ( m_PrecompiledHeader && m_PrecompiledHeader->IsMSVC() )
    {
        pchCPP = m_PrecompiledHeader->GetPrecompiledHeaderCPPFile();
    }
#endif

    // if we depend on any directory lists, we need to use them to get our files
    for ( Dependencies::Iter i = m_StaticDependencies.Begin();
            i != m_StaticDependencies.End();
            i++ )
    {
        // is this a dir list?
        if ( i->GetNode()->GetType() == Node::DIRECTORY_LIST_NODE )
        {
            // get the list of files
            DirectoryListNode * dln = i->GetNode()->CastTo< DirectoryListNode >();
            const Array< FileIO::FileInfo > & files = dln->GetFiles();
            m_DynamicDependencies.SetCapacity( m_DynamicDependencies.GetSize() + files.GetSize() );
            for ( Array< FileIO::FileInfo >::Iter fIt = files.Begin();
                    fIt != files.End();
                    fIt++ )
            {
                // Create the file node (or find an existing one)
                Node * n = nodeGraph.FindNode( fIt->m_Name );
                if ( n == nullptr )
                {
                    n = nodeGraph.CreateFileNode( fIt->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 == pchCPP )
                {
                    continue;
                }
#endif

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

            // unity files
            const Array< AString > & unityFiles = un->GetUnityFileNames();
            for ( Array< AString >::Iter it = unityFiles.Begin();
                    it != unityFiles.End();
                    it++ )
            {
                Node * n = nodeGraph.FindNode( *it );
                if ( n == nullptr )
                {
                    n = nodeGraph.CreateFileNode( *it );
                }
                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, AString::GetEmpty(), true ) == false )
                {
                    return false; // CreateDynamicObjectNode will have emitted error
                }
            }

            // files from unity to build individually
            const Array< UnityNode::FileAndOrigin > & isolatedFiles = un->GetIsolatedFileNames();
            for ( Array< UnityNode::FileAndOrigin >::Iter it = isolatedFiles.Begin();
                    it != isolatedFiles.End();
                    it++ )
            {
                Node * n = nodeGraph.FindNode( it->GetName() );
                if ( n == nullptr )
                {
                    n = nodeGraph.CreateFileNode( it->GetName() );
                }
                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
                const AString & baseDir = it->GetDirListOrigin() ? it->GetDirListOrigin()->GetPath() : AString::GetEmpty();
                if ( CreateDynamicObjectNode( nodeGraph, n, baseDir, false, true ) == false )
                {
                    return false; // CreateDynamicObjectNode will have emitted error
                }
            }
        }
        else if ( i->GetNode()->IsAFile() )
        {
            // a single file, create the object that will compile it
            if ( CreateDynamicObjectNode( nodeGraph, i->GetNode(), AString::GetEmpty() ) == false )
            {
                return false; // CreateDynamicObjectNode will have emitted error
            }
        }
        else
        {
            ASSERT( false ); // unexpected node type
        }
    }

    // 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_PrecompiledHeader )
    {
        m_DynamicDependencies.Append( Dependency( m_PrecompiledHeader ) );
    }

    return true;
}
Esempio n. 6
0
// Commit
//------------------------------------------------------------------------------
/*virtual*/ bool FunctionCopy::Commit( NodeGraph & nodeGraph, const BFFIterator & funcStartIter ) const
{
    // make sure all required variables are defined
    Array< AString > sources( 16, true );
    const BFFVariable * dstFileV;
    if ( !GetStrings( funcStartIter, sources, ".Source", true ) ||
         !GetString( funcStartIter, dstFileV, ".Dest", true ) )
    {
        return false; // GetString will have emitted errors
    }

    // Optional
    AStackString<> sourceBasePath;
    if ( !GetString( funcStartIter, sourceBasePath, ".SourceBasePath", false ) )
    {
        return false; // GetString will have emitted errors
    }

    // Canonicalize the SourceBasePath
    if ( !sourceBasePath.IsEmpty() )
    {
        AStackString<> cleanValue;
        NodeGraph::CleanPath( sourceBasePath, cleanValue );
        PathUtils::EnsureTrailingSlash( cleanValue );
        sourceBasePath = cleanValue;
    }

    // check sources are not paths
    {
        const AString * const end = sources.End();
        for ( const AString * it = sources.Begin(); it != end; ++it )
        {
            const AString & srcFile( *it );

            // source must be a file, not a  path
            if ( PathUtils::IsFolderPath( srcFile ) )
            {
                Error::Error_1105_PathNotAllowed( funcStartIter, this, ".Source", srcFile );
                return false;
            }
        }
    }

    // Pre-build dependencies
    Dependencies preBuildDependencies;
    if ( !GetNodeList( nodeGraph, funcStartIter, ".PreBuildDependencies", preBuildDependencies, false ) )
    {
        return false; // GetNodeList will have emitted an error
    }
    Array< AString > preBuildDependencyNames( preBuildDependencies.GetSize(), false );
    for ( const auto & dep : preBuildDependencies )
    {
        preBuildDependencyNames.Append( dep.GetNode()->GetName() );
    }

    // get source node
    Array< Node * > srcNodes;
    {
        const AString * const end = sources.End();
        for ( const AString * it = sources.Begin(); it != end; ++it )
        {

            Node * srcNode = nodeGraph.FindNode( *it );
            if ( srcNode )
            {
                if ( GetSourceNodes( funcStartIter, srcNode, srcNodes ) == false )
                {
                    return false;
                }
            }
            else
            {
                // source file not defined by use - assume an external file
                srcNodes.Append( nodeGraph.CreateFileNode( *it ) );
            }
        }
    }

    AStackString<> dstFile;
    NodeGraph::CleanPath( dstFileV->GetString(), dstFile );
    const bool dstIsFolderPath = PathUtils::IsFolderPath( dstFile );

    // make all the nodes for copies
    Dependencies copyNodes( srcNodes.GetSize(), false );
    for ( const Node * srcNode : srcNodes )
    {
        AStackString<> dst( dstFile );

        // dest can be a file OR a path.  If it's a path, use the source filename part
        if ( dstIsFolderPath )
        {
            // find filename part of source
            const AString & srcName = srcNode->GetName();

            // If the sourceBasePath is specified (and valid) use the name relative to that
            if ( !sourceBasePath.IsEmpty() && PathUtils::PathBeginsWith( srcName, sourceBasePath ) )
            {
                // Use everything relative to the SourceBasePath
                dst += srcName.Get() + sourceBasePath.GetLength();
            }
            else
            {
                // Use just the file name
                const char * lastSlash = srcName.FindLast( NATIVE_SLASH );
                dst += lastSlash ? ( lastSlash + 1 )    // append filename part if found
                                     : srcName.Get();   // otherwise append whole thing
            }
        }

        // check node doesn't already exist
        if ( nodeGraph.FindNode( dst ) )
        {
            // TODO:C could have a specific error for multiple sources with only 1 output
            // to differentiate from two rules creating the same dst target
            Error::Error_1100_AlreadyDefined( funcStartIter, this, dst );
            return false;
        }

        // create our node
        CopyFileNode * copyFileNode = nodeGraph.CreateCopyFileNode( dst );
        copyFileNode->m_Source = srcNode->GetName();
        copyFileNode->m_PreBuildDependencyNames = preBuildDependencyNames;
        if ( !copyFileNode->Initialize( nodeGraph, funcStartIter, this ) )
        {
            return false; // Initialize will have emitted an error
        }

        copyNodes.Append( Dependency( copyFileNode ) );
    }

    // handle alias creation
    return ProcessAlias( nodeGraph, funcStartIter, copyNodes );
}
Esempio n. 7
0
// Commit
//------------------------------------------------------------------------------
/*virtual*/ bool FunctionExec::Commit( NodeGraph & nodeGraph, const BFFIterator & funcStartIter ) const
{
    // make sure all required variables are defined
    const BFFVariable * outputV;
    const BFFVariable * executableV;
    const BFFVariable * argsV;
    const BFFVariable * workingDirV;
    int32_t expectedReturnCode;
    bool useStdOutAsOutput;
    if ( !GetString( funcStartIter, outputV,        ".ExecOutput", true ) ||
         !GetString( funcStartIter, executableV,    ".ExecExecutable", true ) ||
         !GetString( funcStartIter, argsV,          ".ExecArguments" ) ||
         !GetString( funcStartIter, workingDirV,    ".ExecWorkingDir" ) ||
         !GetInt( funcStartIter, expectedReturnCode, ".ExecReturnCode", 0, false ) ||
         !GetBool( funcStartIter, useStdOutAsOutput, ".ExecUseStdOutAsOutput", false, false))
    {
        return false;
    }

    // check for duplicates
    if ( nodeGraph.FindNode( outputV->GetString() ) != nullptr )
    {
        Error::Error_1100_AlreadyDefined( funcStartIter, this, outputV->GetString() );
        return false;
    }

    // Pre-build dependencies
    Dependencies preBuildDependencies;
    if ( !GetNodeList( nodeGraph, funcStartIter, ".PreBuildDependencies", preBuildDependencies, false ) )
    {
        return false; // GetNodeList will have emitted an error
    }

    // get executable node
    Node * exeNode = nodeGraph.FindNode( executableV->GetString() );
    if ( exeNode == nullptr )
    {
        exeNode = nodeGraph.CreateFileNode( executableV->GetString() );
    }
    else if ( exeNode->IsAFile() == false )
    {
        Error::Error_1103_NotAFile( funcStartIter, this, "ExecExecutable", exeNode->GetName(), exeNode->GetType() );
        return false;
    }

    // source node
    Dependencies inputNodes;
    if ( !GetNodeList( nodeGraph, funcStartIter, ".ExecInput", inputNodes, false ) )
    {
        return false; // GetNodeList will have emitted an error
    }
    else
    {
        // Make sure all nodes are files
        const Dependency * const end = inputNodes.End();
        for (const Dependency * it = inputNodes.Begin();
            it != end;
            ++it)
        {
            Node * node = it->GetNode();
            if (node->IsAFile() == false)
            {
                Error::Error_1103_NotAFile(funcStartIter, this, "ExecInput", node->GetName(), node->GetType());
                return false;
            }
        }
    }

    // optional args
    const AString & arguments(  argsV ?         argsV->GetString()      : AString::GetEmpty() );
    const AString & workingDir( workingDirV ?   workingDirV->GetString(): AString::GetEmpty() );

    // create the TestNode
    Node * outputNode = nodeGraph.CreateExecNode( outputV->GetString(),
                                           inputNodes,
                                           (FileNode *)exeNode,
                                           arguments,
                                           workingDir,
                                           expectedReturnCode,
                                           preBuildDependencies,
                                           useStdOutAsOutput);

    return ProcessAlias( nodeGraph, funcStartIter, outputNode );
}
Esempio n. 8
0
// GetNodeList
//------------------------------------------------------------------------------
/*static*/ bool Function::GetNodeList( NodeGraph & nodeGraph,
									   const BFFIterator & iter, 
									   const Function * function,
									   const char * propertyName,
									   const AString & nodeName,
									   Dependencies & nodes,
									   bool allowCopyDirNodes,
									   bool allowUnityNodes,
									   bool allowRemoveDirNodes )
{
	// get node
	Node * n = nodeGraph.FindNode( nodeName );
	if ( n == nullptr )
	{
		// not found - create a new file node
		n = nodeGraph.CreateFileNode( nodeName );
		nodes.Append( Dependency( n ) );
		return true;
	}

	// found - is it a file?
	if ( n->IsAFile() )
	{
		// found file - just use as is
		nodes.Append( Dependency( n ) );
		return true;
	}

	// found - is it an ObjectList?
	if ( n->GetType() == Node::OBJECT_LIST_NODE )
	{
		// use as-is
		nodes.Append( Dependency( n ) );
		return true;
	}

	// extra types
	if ( allowCopyDirNodes )
	{
		// found - is it an ObjectList?
		if ( n->GetType() == Node::COPY_DIR_NODE )
		{
			// use as-is
			nodes.Append( Dependency( n ) );
			return true;
		}
	}
	if ( allowRemoveDirNodes )
	{
		// found - is it a RemoveDirNode?
		if ( n->GetType() == Node::REMOVE_DIR_NODE )
		{
			// use as-is
			nodes.Append( Dependency( n ) );
			return true;
		}
	}
	if ( allowUnityNodes )
	{
		// found - is it an ObjectList?
		if ( n->GetType() == Node::UNITY_NODE )
		{
			// use as-is
			nodes.Append( Dependency( n ) );
			return true;
		}
	}

	// found - is it a group?
	if ( n->GetType() == Node::ALIAS_NODE )
	{
		AliasNode * an = n->CastTo< AliasNode >();
		const Dependencies & aNodes = an->GetAliasedNodes();
		for ( const Dependency * it = aNodes.Begin(); it != aNodes.End(); ++it )
		{
			// TODO:C by passing as string we'll be looking up again for no reason
			const AString & subName = it->GetNode()->GetName();

			if ( !GetNodeList( nodeGraph, iter, function, propertyName, subName, nodes, allowCopyDirNodes, allowUnityNodes, allowRemoveDirNodes ) )
			{
				return false;
			}
		}
		return true;
	}

	// don't know how to handle this type of node
	Error::Error_1005_UnsupportedNodeType( iter, function, propertyName, n->GetName(), n->GetType() );
	return false;
}
Esempio n. 9
0
// DoDynamicDependencies
//------------------------------------------------------------------------------
/*virtual*/ bool CopyDirNode::DoDynamicDependencies( NodeGraph & nodeGraph, bool forceClean )
{
    (void)forceClean; // dynamic deps are always re-added here, so this is meaningless

    ASSERT( !m_StaticDependencies.IsEmpty() );

    Array< AString > preBuildDependencyNames( m_PreBuildDependencies.GetSize(), false );
    for ( const auto & dep : m_PreBuildDependencies )
    {
        preBuildDependencyNames.Append( dep.GetNode()->GetName() );
    }

    // Iterate all the DirectoryListNodes
    const Dependency * const depEnd = m_StaticDependencies.End();
    for ( const Dependency * dep = m_StaticDependencies.Begin();
          dep != depEnd;
          ++dep )
    {
        // Grab the files
        DirectoryListNode * dln = dep->GetNode()->CastTo< DirectoryListNode >();
        const Array< FileIO::FileInfo > & files = dln->GetFiles();
        const FileIO::FileInfo * const fEnd = files.End();
        for ( const FileIO::FileInfo * fIt = files.Begin();
              fIt != fEnd;
              ++fIt )
        {
            // Create a CopyFileNode for each dynamically discovered file

            // source file (full path)
            const AString & srcFile = fIt->m_Name;

            // source file (relative to base path)
            const AStackString<> srcFileRel( srcFile.Get() + dln->GetPath().GetLength() );

            // source file (as a node)
            Node * srcFileNode = nodeGraph.FindNode( srcFile );
            if ( srcFileNode == nullptr )
            {
                srcFileNode = nodeGraph.CreateFileNode( srcFile );
            }
            else if ( srcFileNode->IsAFile() == false )
            {
                FLOG_ERROR( "CopyDir() Node '%s' is not a FileNode (type: %s)", srcFile.Get(), srcFileNode->GetTypeName() );
                return false;
            }

            // generate dest file name
            const AStackString<> dstFile( m_DestPath );
            (AString &)dstFile += (AString &)srcFileRel;

            // make sure dest doesn't already exist
            Node * n = nodeGraph.FindNode( dstFile );
            if ( n == nullptr )
            {
                CopyFileNode * copyFileNode = nodeGraph.CreateCopyFileNode( dstFile );
                copyFileNode->m_Source = srcFileNode->GetName();
                copyFileNode->m_PreBuildDependencyNames = preBuildDependencyNames; // inherit PreBuildDependencies
                BFFIterator iter;
                if ( !copyFileNode->Initialize( nodeGraph, iter, nullptr ) )
                {
                    return false; // Initialize will have emitted an error
                }
                n = copyFileNode;
            }
            else if ( n->GetType() != Node::COPY_FILE_NODE )
            {
                FLOG_ERROR( "Node '%s' is not a CopyFileNode (type: %s)", n->GetName().Get(), n->GetTypeName() );
                return false;
            }
            else
            {
                CopyFileNode * cn = n->CastTo< CopyFileNode >();
                if ( srcFileNode != cn->GetSourceNode() )
                {
                    FLOG_ERROR( "Conflicting objects found during CopyDir:\n"
                                " File A: %s\n"
                                " File B: %s\n"
                                " Both copy to: %s\n",
                                srcFile.Get(),
                                cn->GetSourceNode()->GetName().Get(),
                                dstFile.Get() );
                    return false;
                }
            }

            m_DynamicDependencies.Append( Dependency( n ) );
        }
    }
    return true;
}
Esempio n. 10
0
// GetPrecompiledHeaderNode
//------------------------------------------------------------------------------
bool FunctionObjectList::GetPrecompiledHeaderNode( NodeGraph & nodeGraph,
                                                   const BFFIterator & iter,
                                                   CompilerNode * compilerNode,
                                                   const BFFVariable * compilerOptions,
                                                   const Dependencies & compilerForceUsing,
                                                   ObjectNode * & precompiledHeaderNode,
                                                   bool deoptimizeWritableFiles,
                                                   bool deoptimizeWritableFilesWithToken,
                                                   bool allowDistribution,
                                                   bool allowCaching,
                                                   const AString& compilerOutputExtension ) const
{
    const BFFVariable * pchInputFile = nullptr;
    const BFFVariable * pchOutputFile = nullptr;
    const BFFVariable * pchOptions = nullptr;
    if ( !GetString( iter, pchInputFile, ".PCHInputFile" ) ||
         !GetString( iter, pchOutputFile, ".PCHOutputFile" ) ||
         !GetString( iter, pchOptions, ".PCHOptions" ) )
    {
        return false;
    }

    precompiledHeaderNode = nullptr;

    AStackString<> pchObjectName;

    if ( pchInputFile )
    {
        if ( !pchOutputFile || !pchOptions )
        {
            Error::Error_1300_MissingPCHArgs( iter, this );
            return false;
        }

        Node * pchInputNode = nodeGraph.FindNode( pchInputFile->GetString() );
        if ( pchInputNode )
        {
            // is it a file?
            if ( pchInputNode->IsAFile() == false )
            {
                Error::Error_1103_NotAFile( iter, this, "PCHInputFile", pchInputNode->GetName(), pchInputNode->GetType() );
                return false;
            }
        }
        else
        {
            // Create input node
            pchInputNode = nodeGraph.CreateFileNode( pchInputFile->GetString() );
        }

        if ( nodeGraph.FindNode( pchOutputFile->GetString() ) )
        {
            Error::Error_1301_AlreadyDefinedPCH( iter, this, pchOutputFile->GetString().Get() );
            return false;
        }

        uint32_t pchFlags = ObjectNode::DetermineFlags( compilerNode, pchOptions->GetString(), true, false );
        if ( pchFlags & ObjectNode::FLAG_MSVC )
        {
            // sanity check arguments
            bool foundYcInPCHOptions = false;
            bool foundFpInPCHOptions = false;

            // Find /Fo option to obtain pch object file name
            Array< AString > pchTokens;
            pchOptions->GetString().Tokenize( pchTokens );
            for ( const AString & token : pchTokens )
            {
                if ( ObjectNode::IsStartOfCompilerArg_MSVC( token, "Fo" ) )
                {
                    // Extract filename (and remove quotes if found)
                    pchObjectName = token.Get() + 3;
                    pchObjectName.Trim( pchObjectName.BeginsWith( '"' ) ? 1 : 0, pchObjectName.EndsWith( '"' ) ? 1 : 0 );

                    // Auto-generate name?
                    if ( pchObjectName == "%3" )
                    {
                        // example 'PrecompiledHeader.pch' to 'PrecompiledHeader.pch.obj'
                        pchObjectName = pchOutputFile->GetString();
                        pchObjectName += compilerOutputExtension;
                    }
                }
                else if ( ObjectNode::IsStartOfCompilerArg_MSVC( token, "Yc" ) )
                {
                    foundYcInPCHOptions = true;
                }
                else if ( ObjectNode::IsStartOfCompilerArg_MSVC( token, "Fp" ) )
                {
                    foundFpInPCHOptions = true;
                }
            }

            // PCH must have "Create PCH" (e.g. /Yc"PrecompiledHeader.h")
            if ( foundYcInPCHOptions == false )
            {
                Error::Error_1302_MissingPCHCompilerOption( iter, this, "Yc", "PCHOptions" );
                return false;
            }
            // PCH must have "Precompiled Header to Use" (e.g. /Fp"PrecompiledHeader.pch")
            if ( foundFpInPCHOptions == false )
            {
                Error::Error_1302_MissingPCHCompilerOption( iter, this, "Fp", "PCHOptions" );
                return false;
            }
            // PCH must have object output option (e.g. /Fo"PrecompiledHeader.obj")
            if ( pchObjectName.IsEmpty() )
            {
                Error::Error_1302_MissingPCHCompilerOption( iter, this, "Fo", "PCHOptions" );
                return false;
            }

            // Check Compiler Options
            bool foundYuInCompilerOptions = false;
            bool foundFpInCompilerOptions = false;
            Array< AString > compilerTokens;
            compilerOptions->GetString().Tokenize( compilerTokens );
            for ( const AString & token : compilerTokens )
            {
                if ( ObjectNode::IsStartOfCompilerArg_MSVC( token, "Yu" ) )
                {
                    foundYuInCompilerOptions = true;
                }
                else if ( ObjectNode::IsStartOfCompilerArg_MSVC( token, "Fp" ) )
                {
                    foundFpInCompilerOptions = true;
                }
            }

            // Object using the PCH must have "Use PCH" option (e.g. /Yu"PrecompiledHeader.h")
            if ( foundYuInCompilerOptions == false )
            {
                Error::Error_1302_MissingPCHCompilerOption( iter, this, "Yu", "CompilerOptions" );
                return false;
            }
            // Object using the PCH must have "Precompiled header to use" (e.g. /Fp"PrecompiledHeader.pch")
            if ( foundFpInCompilerOptions == false )
            {
                Error::Error_1302_MissingPCHCompilerOption( iter, this, "Fp", "CompilerOptions" );
                return false;
            }
        }

        precompiledHeaderNode = nodeGraph.CreateObjectNode( pchOutputFile->GetString(),
                                                     pchInputNode,
                                                     compilerNode,
                                                     pchOptions->GetString(),
                                                     AString::GetEmpty(),
                                                     nullptr,
                                                     pchFlags,
                                                     compilerForceUsing,
                                                     deoptimizeWritableFiles,
                                                     deoptimizeWritableFilesWithToken,
                                                     allowDistribution,
                                                     allowCaching,
                                                     nullptr, AString::GetEmpty(), 0 ); // preprocessor args not supported
        precompiledHeaderNode->m_PCHObjectFileName = pchObjectName;
    }

    return true;
}