浏览代码

Add build graph visitor.

Signed-off-by: John <[email protected]>
John 3 年之前
父节点
当前提交
c9eea04800

+ 8 - 0
Code/Tools/TestImpactFramework/Runtime/Common/Code/Include/Static/Artifact/Static/TestImpactTargetDescriptor.h

@@ -15,6 +15,13 @@
 
 namespace TestImpact
 {
+    //! Enumeration to facilitate runtime determination of build target types.
+    enum class TargetType : AZ::u8
+    {
+        TestTarget,
+        ProductionTarget
+    };
+
     //! Pairing between a given autogen input source and the generated output source(s).
     struct AutogenPairs
     {
@@ -45,6 +52,7 @@ namespace TestImpact
     struct TargetDescriptor
     {
         AZStd::string m_name; //!< Build target name.
+        TargetType m_type; //!< The type of target.
         RepoPath m_path; //!< Source path to target location in source tree (relative to repository root).
         TargetSources m_sources; //!< Sources that are parented by this build target.
         TargetDependencies m_dependencies; //!< Build and runtime dependencies.

+ 141 - 1
Code/Tools/TestImpactFramework/Runtime/Common/Code/Include/Static/BuildTarget/Common/TestImpactBuildGraph.h

@@ -11,13 +11,31 @@
 #include <BuildTarget/Common/TestImpactBuildTargetException.h>
 #include <BuildTarget/Common/TestImpactBuildTargetList.h>
 
+#include <AzCore/std/containers/queue.h>
+#include <AzCore/std/containers/stack.h>
 #include <AzCore/std/containers/unordered_map.h>
 #include <AzCore/std/containers/unordered_set.h>
+#include <AzCore/std/functional.h>
 
 namespace TestImpact
 {
+    //! Result to return when visiting vertices in the build graph.
+    enum class BuildGraphVertexVisitResult : AZ::u8
+    {
+        Continue, //!< Continue traversing the build graph.
+        AbortBranchTraversal, //!< Abort traversal of this particular branch in the build graph.
+        AbortGraphTraversal //!< Abort traversal of the build graph.
+    };
+
     template<typename ProductionTarget, typename TestTarget>
     struct BuildGraphVertex;
+
+    //! Visitor callback for when traversing the build graphs.
+    //! @param vertex The current vertex to visit in the build graph.
+    //! @param distance The distance of this vertex to the root build target in the build graph.
+    //! @returns The traversal result for the build graph to act upon.
+    template<typename ProductionTarget, typename TestTarget>
+    using BuildGraphVertexVisitor = AZStd::function<BuildGraphVertexVisitResult(const BuildGraphVertex<ProductionTarget, TestTarget>& vertex, size_t distance)>;
     
     //! Build graph target set for dependencies or dependers.
     template<typename ProductionTarget, typename TestTarget>
@@ -59,8 +77,35 @@ namespace TestImpact
         //! Returns the vertex for the specified build target, else throws `BuildTargetException`.
         const BuildGraphVertex<ProductionTarget, TestTarget>& GetVertexOrThrow(
             const BuildTarget<ProductionTarget, TestTarget>& buildTarget) const;
-            
+
+        //! Walks the specified target's build dependencies.
+        void WalkBuildDependencies(
+            const BuildTarget<ProductionTarget, TestTarget>& buildTarget,
+            const BuildGraphVertexVisitor<ProductionTarget, TestTarget>& visitor) const;
+
+        //! Walks the specified target's build dependers.
+        void WalkBuildDependers(
+            const BuildTarget<ProductionTarget, TestTarget>& buildTarget,
+            const BuildGraphVertexVisitor<ProductionTarget, TestTarget>& visitor) const;
+
+        //! Walks the specified target's runtime dependencies.
+        void WalkRuntimeDependencies(
+            const BuildTarget<ProductionTarget, TestTarget>& buildTarget,
+            const BuildGraphVertexVisitor<ProductionTarget, TestTarget>& visitor) const;
+
+        //! Walks the specified target's runtime dependers.
+        void WalkRuntimeDependers(
+            const BuildTarget<ProductionTarget, TestTarget>& buildTarget,
+            const BuildGraphVertexVisitor<ProductionTarget, TestTarget>& visitor) const;
+
     private:
+        //! Generic function for walking the specified target's build grpah.
+        void WalkTargetBuildGraphSet(
+            TargetBuildGraph<ProductionTarget, TestTarget> BuildGraphVertex<ProductionTarget, TestTarget>::*graph,
+            TargetBuildGraphSet<ProductionTarget, TestTarget> TargetBuildGraph<ProductionTarget, TestTarget>::*set,
+            const BuildTarget<ProductionTarget, TestTarget>& buildTarget,
+            const BuildGraphVertexVisitor<ProductionTarget, TestTarget>& visitor) const;
+
         //! Map of all graph vertices, identified by build target.
         AZStd::unordered_map<BuildTarget<ProductionTarget, TestTarget>, BuildGraphVertex<ProductionTarget, TestTarget>>
             m_buildGraphVertices;
@@ -160,6 +205,101 @@ namespace TestImpact
 
         return *vertex;
     }
+
+    template<typename ProductionTarget, typename TestTarget>
+    void BuildGraph<ProductionTarget, TestTarget>::WalkTargetBuildGraphSet(
+        TargetBuildGraph<ProductionTarget, TestTarget> BuildGraphVertex<ProductionTarget, TestTarget>::*graph,
+        TargetBuildGraphSet<ProductionTarget, TestTarget> TargetBuildGraph<ProductionTarget, TestTarget>::*set,
+        const BuildTarget<ProductionTarget, TestTarget>& buildTarget,
+        const BuildGraphVertexVisitor<ProductionTarget, TestTarget>& visitor) const
+    {
+        AZStd::queue<const BuildGraphVertex<ProductionTarget, TestTarget>*> vertexQueue;
+        AZStd::unordered_set<const BuildGraphVertex<ProductionTarget, TestTarget>*> visitedVertices;
+        AZStd::unordered_map<const BuildGraphVertex<ProductionTarget, TestTarget>*, size_t> vertexDistances;
+
+        // Skip visiting the root vertex and start visiting its children instead
+        const auto& parentVertex = GetVertexOrThrow(buildTarget);
+        visitedVertices.insert(&parentVertex);
+        for (const auto* child : parentVertex.*graph.*set)
+        {
+            vertexQueue.push(child);
+            vertexDistances[child] = 1;
+        }
+
+        while (!vertexQueue.empty())
+        {
+            const auto* vertex = vertexQueue.front();
+            vertexQueue.pop();
+
+            if (const auto result = visitor(*vertex, vertexDistances[vertex]);
+                result == BuildGraphVertexVisitResult::AbortGraphTraversal)
+            {
+                return;
+            }
+            else if (result == BuildGraphVertexVisitResult::AbortBranchTraversal)
+            {
+                continue;
+            }
+
+            for (const auto* child : vertex->*graph.*set)
+            {
+                if (!visitedVertices.contains(child))
+                {
+                    visitedVertices.insert(child);
+                    vertexQueue.push(child);
+                    vertexDistances[child] = vertexDistances[vertex] + 1;
+                }
+            }
+        }
+    }
+
+    template<typename ProductionTarget, typename TestTarget>
+    void BuildGraph<ProductionTarget, TestTarget>::WalkBuildDependencies(
+        const BuildTarget<ProductionTarget, TestTarget>& buildTarget,
+        const BuildGraphVertexVisitor<ProductionTarget, TestTarget>& visitor) const
+    {
+        WalkTargetBuildGraphSet(
+            &BuildGraphVertex<ProductionTarget, TestTarget>::m_dependencies,
+            &TargetBuildGraph<ProductionTarget, TestTarget>::m_build,
+            buildTarget,
+            visitor);
+    }
+
+    template<typename ProductionTarget, typename TestTarget>
+    void BuildGraph<ProductionTarget, TestTarget>::WalkBuildDependers(
+        const BuildTarget<ProductionTarget, TestTarget>& buildTarget,
+        const BuildGraphVertexVisitor<ProductionTarget, TestTarget>& visitor) const
+    {
+        WalkTargetBuildGraphSet(
+            &BuildGraphVertex<ProductionTarget, TestTarget>::m_dependers,
+            &TargetBuildGraph<ProductionTarget, TestTarget>::m_build,
+            buildTarget,
+            visitor);
+    }
+
+    template<typename ProductionTarget, typename TestTarget>
+    void BuildGraph<ProductionTarget, TestTarget>::WalkRuntimeDependencies(
+        const BuildTarget<ProductionTarget, TestTarget>& buildTarget,
+        const BuildGraphVertexVisitor<ProductionTarget, TestTarget>& visitor) const
+    {
+        WalkTargetBuildGraphSet(
+            &BuildGraphVertex<ProductionTarget, TestTarget>::m_dependencies,
+            &TargetBuildGraph<ProductionTarget, TestTarget>::m_runtime,
+            buildTarget,
+            visitor);
+    }
+
+    template<typename ProductionTarget, typename TestTarget>
+    void BuildGraph<ProductionTarget, TestTarget>::WalkRuntimeDependers(
+        const BuildTarget<ProductionTarget, TestTarget>& buildTarget,
+        const BuildGraphVertexVisitor<ProductionTarget, TestTarget>& visitor) const
+    {
+        WalkTargetBuildGraphSet(
+            &BuildGraphVertex<ProductionTarget, TestTarget>::m_dependers,
+            &TargetBuildGraph<ProductionTarget, TestTarget>::m_runtime,
+            buildTarget,
+            visitor);
+    }
 } // namespace TestImpact
 
 namespace AZStd

+ 1 - 22
Code/Tools/TestImpactFramework/Runtime/Common/Code/Include/Static/BuildTarget/Common/TestImpactBuildTarget.h

@@ -15,13 +15,6 @@
 
 namespace TestImpact
 {
-    //! Enumeration to facilitate runtime determination of build target types. 
-    enum class BuildTargetType : AZ::u8
-    {
-        TestTarget,
-        ProductionTarget
-    };
-
     //! Common wrapper for repository build targets, be they production targets or test targets.
     template<typename ProductionTarget, typename TestTarget>
     class BuildTarget
@@ -41,10 +34,7 @@ namespace TestImpact
     
         //! Returns the production target pointer for this parent (if any), otherwise nullptr.
         const ProductionTarget* GetProductionTarget() const;
-
-        //! Returns the target type at runtime.
-        BuildTargetType GetTargetType() const;
-    
+            
         //! Visits the target type at compile time.
         template<typename Visitor>
         void Visit(const Visitor& visitor) const;
@@ -60,22 +50,17 @@ namespace TestImpact
         static constexpr bool IsTestTarget =
             AZStd::is_same_v<TestTarget, AZStd::remove_const_t<AZStd::remove_pointer_t<AZStd::decay_t<Target>>>>;
         AZStd::variant<const TestTarget*, const ProductionTarget*> m_target;
-
-        //! The build target type (either production or test)/.
-        BuildTargetType m_type;
     };
 
     template<typename ProductionTarget, typename TestTarget>
     BuildTarget<ProductionTarget, TestTarget>::BuildTarget(const TestTarget* testTarget)
         : m_target(testTarget)
-        , m_type(BuildTargetType::TestTarget)
     {
     }
 
     template<typename ProductionTarget, typename TestTarget>
     BuildTarget<ProductionTarget, TestTarget>::BuildTarget(const ProductionTarget* productionTarget)
         : m_target(productionTarget)
-        , m_type(BuildTargetType::ProductionTarget)
     {
     }
 
@@ -124,12 +109,6 @@ namespace TestImpact
         return productionTarget;
     }
 
-    template<typename ProductionTarget, typename TestTarget>
-    BuildTargetType BuildTarget<ProductionTarget, TestTarget>::GetTargetType() const
-    {       
-        return m_type;
-    }
-
     template<typename ProductionTarget, typename TestTarget>
     template<typename Visitor>
     void BuildTarget<ProductionTarget, TestTarget>::Visit(const Visitor& visitor) const

+ 3 - 3
Code/Tools/TestImpactFramework/Runtime/Common/Code/Include/Static/Dependency/TestImpactTestSelectorAndPrioritizer.h

@@ -218,7 +218,7 @@ namespace TestImpact
             for (const auto& parentTarget : sourceDependency.GetParentTargets())
             {
 
-                if (parentTarget.GetTargetType() == BuildTargetType::ProductionTarget)
+                if (parentTarget.GetTarget()->GetType() == TargetType::ProductionTarget)
                 {
                     // Parent Targets: Yes
                     // Coverage Data : No
@@ -254,7 +254,7 @@ namespace TestImpact
                 {
                     for (const auto& parentTarget : sourceDependency.GetParentTargets())
                     {
-                        if (parentTarget.GetTargetType() == BuildTargetType::ProductionTarget)
+                        if (parentTarget.GetTarget()->GetType() == TargetType::ProductionTarget)
                         {
                             // Parent Targets: Yes
                             // Coverage Data : Yes
@@ -284,7 +284,7 @@ namespace TestImpact
                 {
                     for (const auto& parentTarget : sourceDependency.GetParentTargets())
                     {
-                        if (parentTarget.GetTargetType() == BuildTargetType::ProductionTarget)
+                        if (parentTarget.GetTarget()->GetType() == TargetType::ProductionTarget)
                         {
                             // Parent Targets: Yes
                             // Coverage Data : No

+ 3 - 0
Code/Tools/TestImpactFramework/Runtime/Common/Code/Include/Static/Target/Common/TestImpactTarget.h

@@ -22,6 +22,9 @@ namespace TestImpact
         //! Returns the build target name.
         const AZStd::string& GetName() const;
 
+        //! Returns the target type at runtime.
+        TargetType GetType() const;
+
         //! Returns the path in the source tree to the build target location.
         const RepoPath& GetPath() const;
 

+ 0 - 2
Code/Tools/TestImpactFramework/Runtime/Common/Code/Include/Static/Target/Common/TestImpactTargetList.h

@@ -26,8 +26,6 @@ namespace TestImpact
     class TargetList
     {
     public:
-        using TargetType = Target;
-
         TargetList(AZStd::vector<Target>&& targets);
 
         //! Returns the targets in the collection.

+ 23 - 3
Code/Tools/TestImpactFramework/Runtime/Common/Code/Source/Artifact/Factory/TestImpactTargetDescriptorFactory.cpp

@@ -69,6 +69,7 @@ namespace TestImpact
         {
             "target",
             "name",
+            "type",
             "output_name",
             "path",
             "sources",
@@ -77,13 +78,16 @@ namespace TestImpact
             "output",
             "dependencies",
             "build",
-            "runtime"
+            "runtime",
+            "production",
+            "test"
         };
 
         enum
         {
             TargetKey,
             NameKey,
+            TargetTypeKey,
             OutputNameKey,
             PathKey,
             SourcesKey,
@@ -92,7 +96,9 @@ namespace TestImpact
             OutputKey,
             DependenciesKey,
             BuildDependenciesKey,
-            RuntimeDependenciesKey
+            RuntimeDependenciesKey,
+            ProductionTargetTypeKey,
+            TestTargetTypeKey
         };
 
         AZ_TestImpact_Eval(!autogenMatcher.empty(), ArtifactException, "Autogen matcher cannot be empty");
@@ -108,7 +114,21 @@ namespace TestImpact
         const auto& target = buildTarget[Keys[TargetKey]];
         descriptor.m_name = target[Keys[NameKey]].GetString();
         descriptor.m_outputName = target[Keys[OutputNameKey]].GetString();
-        descriptor.m_path = target["path"].GetString();
+        descriptor.m_path = target[Keys[PathKey]].GetString();
+
+        if (const auto targetTypeString = target[Keys[TargetTypeKey]].GetString();
+            strcmp(targetTypeString, Keys[ProductionTargetTypeKey]) == 0)
+        {
+            descriptor.m_type = TargetType::ProductionTarget;
+        }
+        else if (strcmp(targetTypeString, Keys[TestTargetTypeKey]) == 0)
+        {
+            descriptor.m_type = TargetType::TestTarget;
+        }
+        else
+        {
+            throw(ArtifactException("Unexpected target type"));
+        }
 
         AZ_TestImpact_Eval(!descriptor.m_name.empty(), ArtifactException, "Target name cannot be empty");
         AZ_TestImpact_Eval(!descriptor.m_outputName.empty(), ArtifactException, "Target output name cannot be empty");

+ 5 - 0
Code/Tools/TestImpactFramework/Runtime/Common/Code/Source/Target/Common/TestImpactTarget.cpp

@@ -20,6 +20,11 @@ namespace TestImpact
         return m_descriptor.m_name;
     }
 
+    TargetType Target::GetType() const
+    {
+        return m_descriptor.m_type;
+    }
+
     const RepoPath& Target::GetPath() const
     {
         return m_descriptor.m_path;

+ 1 - 0
Code/Tools/TestImpactFramework/Runtime/Native/Code/Source/Target/Native/TestImpactNativeTargetListCompiler.cpp

@@ -32,6 +32,7 @@ namespace TestImpact
             // If this build target has an associated test artifact then it is a test target, otherwise it is a production target
             if (auto&& testTargetMeta = testTargetMetaMap.find(descriptor.m_name); testTargetMeta != testTargetMetaMap.end())
             {
+                AZ_TestImpact_Eval(descriptor.m_type == TargetType::TestTarget, ArtifactException, "Target has associated target meta but is a production target");
                 testTargets.emplace_back(AZStd::move(descriptor), AZStd::move(testTargetMeta->second));
             }
             else

+ 6 - 1
Code/Tools/TestImpactFramework/Runtime/Python/Code/Source/Target/Python/TestImpactPythonTargetListCompiler.cpp

@@ -24,13 +24,18 @@ namespace TestImpact
 
         for (auto&& descriptor : buildTargetDescriptors)
         {
-            productionTargets.emplace_back(AZStd::move(descriptor));
+            if(descriptor.m_type != TargetType::TestTarget)
+            {
+                // Python test targets are compiled programmatically so ignore any other test targets
+                productionTargets.emplace_back(AZStd::move(descriptor));
+            }
         }
 
         for (auto&& testTargetMeta : testTargetMetaMap)
         {
             TargetDescriptor descriptor;
             descriptor.m_name = testTargetMeta.first;
+            descriptor.m_type = TargetType::ProductionTarget;
             descriptor.m_sources.m_staticSources.push_back(testTargetMeta.second.m_scriptMeta.m_scriptPath);
 
             testTargets.emplace_back(AZStd::move(descriptor), AZStd::move(testTargetMeta.second));

+ 11 - 1
cmake/LYTestWrappers.cmake

@@ -261,9 +261,19 @@ function(ly_add_test)
     # Check to see whether or not this test target has been stored in the global list for walking by the test impact analysis framework
     get_property(all_tests GLOBAL PROPERTY LY_ALL_TESTS)
     if(NOT "${test_target}" IN_LIST all_tests)
+        # Extract the test target name from the namespace::target_name composite
+        string(REPLACE "::" ";" test_components ${test_target})
+        list(LENGTH test_components num_test_components)
+        if(num_test_components GREATER 1)
+            list(GET test_components 1 test_name)
+        else()
+            set(test_name ${test_components})
+        endif()
+        set_property(GLOBAL APPEND PROPERTY O3DE_ALL_TESTS_DE_NAMSPACED ${test_name})
+
         # This is the first reference to this test target so add it to the global list
         set_property(GLOBAL APPEND PROPERTY LY_ALL_TESTS ${test_target})
-        set_property(GLOBAL  PROPERTY LY_ALL_TESTS_${test_target}_TEST_LIBRARY ${ly_add_test_TEST_LIBRARY})
+        set_property(GLOBAL PROPERTY LY_ALL_TESTS_${test_target}_TEST_LIBRARY ${ly_add_test_TEST_LIBRARY})
     endif()
     # Add the test suite and timeout value to the test target params
     set(LY_TEST_PARAMS "${LY_TEST_PARAMS}#${ly_add_test_TEST_SUITE}")

+ 33 - 16
cmake/TestImpactFramework/LYTestImpactFramework.cmake

@@ -363,25 +363,22 @@ function(ly_extract_target_dependencies INPUT_DEPENDENCY_LIST OUTPUT_DEPENDENCY_
         # Extract just the target name, ignoring the namespace
         if(TARGET ${qualified_target_name})
             set(target_to_add "")
-            string(REPLACE "::" ";" target_name_components ${qualified_target_name})
-            list(LENGTH target_name_components num_name_components)
-            if(num_name_components GREATER 1)
-                list(GET target_name_components 0 target_namespace)
-                list(GET target_name_components 1 target_name)
-                set(target_to_add ${target_name})
-            else()
-                set(target_to_add ${target_name})
-            endif()
-
-            if(NOT ${target_to_add} STREQUAL "")
+            get_target_property(is_imported ${qualified_target_name} IMPORTED)
+            get_target_property(is_gem_module ${qualified_target_name} GEM_MODULE)
+            # Skip third party dependencies
+            if(NOT is_imported OR is_gem_module)
+                string(REPLACE "::" ";" target_name_components ${qualified_target_name})
+                list(LENGTH target_name_components num_name_components)
+                if(num_name_components GREATER 1)
+                    list(GET target_name_components 1 target_name)
+                    set(target_to_add ${target_name})
+                else()
+                    set(target_to_add ${qualified_target_name})
+                endif()
                 # Extract the targets this target may alias
                 ly_extract_aliased_target_dependencies(${target_to_add} de_aliased_targets)
                 foreach(de_aliased_target ${de_aliased_targets})
-                    # Skip third party dependencies
-                    get_target_property(is_imported ${qualified_target_name} IMPORTED)
-                    if(NOT is_imported)
-                        list(APPEND dependencies "\"${de_aliased_target}\"")
-                    endif()
+                    list(APPEND dependencies "\"${de_aliased_target}\"")
                 endforeach()
             endif()
         endif()
@@ -392,6 +389,23 @@ function(ly_extract_target_dependencies INPUT_DEPENDENCY_LIST OUTPUT_DEPENDENCY_
     set(${OUTPUT_DEPENDENCY_LIST} ${dependencies} PARENT_SCOPE)
 endfunction()
 
+#! ly_get_target_type: retrieves the type of target (either production or test target).
+#
+# \arg:TARGET target to get the type for
+# \arg:TARGET_TYPE the retrieved target type
+function(ly_get_target_type TARGET TARGET_TYPE)
+    get_property(O3DE_ALL_TESTS_DE_NAMSPACED GLOBAL PROPERTY O3DE_ALL_TESTS_DE_NAMSPACED)
+    if(${target_name} IN_LIST O3DE_ALL_TESTS_DE_NAMSPACED)
+        set(target_type "test")
+    else()
+        set(target_type "production")
+    endif()
+
+    set(${TARGET_TYPE} ${target_type} PARENT_SCOPE)
+    set(${TYPE_META} ${type_meta} PARENT_SCOPE)
+
+endfunction()
+
 #! ly_test_impact_export_source_target_mappings: exports the static source to target mappings to file.
 #
 # \arg:MAPPING_TEMPLATE_FILE path to source to target template file
@@ -411,6 +425,9 @@ function(ly_test_impact_export_source_target_mappings MAPPING_TEMPLATE_FILE)
         get_target_property(target_path_abs ${target} SOURCE_DIR)
         file(RELATIVE_PATH target_path ${LY_ROOT_FOLDER} ${target_path_abs})
 
+        # Target type
+        ly_get_target_type(${target_name} target_type)
+
         # Output name
         get_target_property(target_output_name ${target} OUTPUT_NAME)
         if (target_output_name STREQUAL "target_output_name-NOTFOUND")

+ 12 - 11
cmake/TestImpactFramework/SourceToTargetMapping.in

@@ -1,17 +1,7 @@
 {
-    "sources": {
-        "input": [
-${autogen_input_files}
-        ],
-        "output": [
-${autogen_output_files}
-        ],
-        "static": [
-${static_sources}
-        ]
-    },
     "target": {
         "name": "${target_name}",
+        "type": "${target_type}",
         "output_name": "${target_output_name}",
         "path": "${target_path}",
         "dependencies": {
@@ -22,5 +12,16 @@ ${static_sources}
                 ${runtime_dependencies}
             ]
         }
+    },
+    "sources": {
+        "input": [
+${autogen_input_files}
+        ],
+        "output": [
+${autogen_output_files}
+        ],
+        "static": [
+${static_sources}
+        ]
     }
 }