Merge pull request #3049 from aws-lumberyard-dev/cgalvan/gitflow_210811
Merged stabilization/2106 to main
This commit is contained in:
-55
@@ -1,55 +0,0 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.paths
|
||||
|
||||
sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests"))
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
from editor_python_test_tools.editor_test_helper import EditorTestHelper
|
||||
|
||||
|
||||
class TestDebuggerDebugCVarsWorks(EditorTestHelper):
|
||||
def __init__(self):
|
||||
EditorTestHelper.__init__(self, log_prefix="Debugger_DebugCVarsWorks", args=["level"])
|
||||
|
||||
def run_test(self):
|
||||
"""
|
||||
Summary:
|
||||
C2789148 Vegetation Debug CVars are enabled when the Debugger component is present
|
||||
|
||||
Expected Result:
|
||||
The following commands are available in the Editor only when the Vegetation Debugger Level component is present:
|
||||
veg_debugDumpReport (Command)
|
||||
veg_debugRefreshAllAreas (Command)
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# Create empty level
|
||||
self.test_success = self.create_level(
|
||||
self.args["level"],
|
||||
heightmap_resolution=1024,
|
||||
heightmap_meters_per_pixel=1,
|
||||
terrain_texture_resolution=4096,
|
||||
use_terrain=False,
|
||||
)
|
||||
|
||||
# Initially run the command in console without Debugger component
|
||||
general.run_console("veg_debugDumpReport")
|
||||
|
||||
# Add the Vegetation Debugger component to the Level Inspector
|
||||
hydra.add_level_component("Vegetation Debugger")
|
||||
|
||||
# Run a command again after adding the Vegetation debugger
|
||||
general.run_console("veg_debugRefreshAllAreas")
|
||||
|
||||
|
||||
test = TestDebuggerDebugCVarsWorks()
|
||||
test.run()
|
||||
@@ -1,56 +0,0 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import logging
|
||||
|
||||
# Bail on the test if ly_test_tools doesn't exist.
|
||||
pytest.importorskip("ly_test_tools")
|
||||
import ly_test_tools.environment.file_system as file_system
|
||||
import editor_python_test_tools.hydra_test_utils as hydra
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@pytest.mark.parametrize("level", ["tmp_level"])
|
||||
@pytest.mark.usefixtures("automatic_process_killer")
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
class TestDebugger(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_teardown(self, request, workspace, project, level):
|
||||
# Cleanup our temp level
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
|
||||
|
||||
def teardown():
|
||||
# Cleanup our temp level
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
|
||||
|
||||
request.addfinalizer(teardown)
|
||||
|
||||
@pytest.mark.test_case_id("C2789148")
|
||||
@pytest.mark.SUITE_periodic
|
||||
@pytest.mark.dynveg_misc
|
||||
def test_Debugger_DebugCVarsWork(self, request, editor, level, workspace, launcher_platform):
|
||||
cfg_args = [level]
|
||||
|
||||
expected_lines = [
|
||||
"Debugger_DebugCVarsWorks: test started",
|
||||
"[Warning] Unknown command: veg_debugDumpReport",
|
||||
"[CONSOLE] Executing console command 'veg_debugRefreshAllAreas'",
|
||||
"Debugger_DebugCVarsWorks: result=SUCCESS",
|
||||
]
|
||||
|
||||
hydra.launch_and_validate_results(
|
||||
request,
|
||||
test_directory,
|
||||
editor,
|
||||
"Debugger_DebugCVarsWorks.py",
|
||||
expected_lines=expected_lines,
|
||||
cfg_args=cfg_args
|
||||
)
|
||||
+1
@@ -104,6 +104,7 @@ class TestGeneralGraphFunctionality(object):
|
||||
|
||||
@pytest.mark.test_case_id("C17488412")
|
||||
@pytest.mark.SUITE_periodic
|
||||
@pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/2201")
|
||||
def test_LandscapeCanvas_GraphClosed_OnEntityDelete(self, request, editor, level, launcher_platform):
|
||||
cfg_args = [level]
|
||||
|
||||
|
||||
@@ -163,6 +163,8 @@ ly_add_target(
|
||||
editor_files.cmake
|
||||
PLATFORM_INCLUDE_FILES
|
||||
Platform/${PAL_PLATFORM_NAME}/editor_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
|
||||
TARGET_PROPERTIES
|
||||
LY_INSTALL_GENERATE_RUN_TARGET TRUE
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
3rdParty::Qt::Core
|
||||
|
||||
@@ -277,7 +277,7 @@ namespace AZ::IO
|
||||
//! then their hash values are also equal
|
||||
//! For example : path "a//b" equals "a/b", the
|
||||
//! hash value of "a//b" would also equal the hash value of "a/b"
|
||||
constexpr size_t hash_value(const PathView& pathToHash) noexcept;
|
||||
size_t hash_value(const PathView& pathToHash) noexcept;
|
||||
|
||||
// path.comparison
|
||||
constexpr bool operator==(const PathView& lhs, const PathView& rhs) noexcept;
|
||||
@@ -622,7 +622,7 @@ namespace AZ::IO
|
||||
//! For example : path "a//b" equals "a/b", the
|
||||
//! hash value of "a//b" would also equal the hash value of "a/b"
|
||||
template <typename StringType>
|
||||
constexpr size_t hash_value(const BasicPath<StringType>& pathToHash);
|
||||
size_t hash_value(const BasicPath<StringType>& pathToHash);
|
||||
|
||||
// path.append
|
||||
template <typename StringType>
|
||||
|
||||
@@ -1951,7 +1951,7 @@ namespace AZ::IO
|
||||
}
|
||||
|
||||
template <typename StringType>
|
||||
constexpr size_t hash_value(const BasicPath<StringType>& pathToHash)
|
||||
inline size_t hash_value(const BasicPath<StringType>& pathToHash)
|
||||
{
|
||||
return AZStd::hash<BasicPath<StringType>>{}(pathToHash);
|
||||
}
|
||||
@@ -2082,13 +2082,28 @@ namespace AZStd
|
||||
template <>
|
||||
struct hash<AZ::IO::PathView>
|
||||
{
|
||||
constexpr size_t operator()(const AZ::IO::PathView& pathToHash) noexcept
|
||||
/// Path is using FNV-1a algorithm 64 bit version.
|
||||
static size_t hash_path(AZStd::string_view pathSegment, const char pathSeparator)
|
||||
{
|
||||
size_t hash = 14695981039346656037ULL;
|
||||
constexpr size_t fnvPrime = 1099511628211ULL;
|
||||
|
||||
for (const char first : pathSegment)
|
||||
{
|
||||
hash ^= static_cast<size_t>((pathSeparator == AZ::IO::PosixPathSeparator)
|
||||
? first : tolower(first));
|
||||
hash *= fnvPrime;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
size_t operator()(const AZ::IO::PathView& pathToHash) noexcept
|
||||
{
|
||||
auto pathParser = AZ::IO::parser::PathParser::CreateBegin(pathToHash.Native(), pathToHash.m_preferred_separator);
|
||||
size_t hash_value = 0;
|
||||
while (pathParser)
|
||||
{
|
||||
AZStd::hash_combine(hash_value, AZStd::hash<AZStd::string_view>{}(*pathParser));
|
||||
AZStd::hash_combine(hash_value, hash_path(*pathParser, pathToHash.m_preferred_separator));
|
||||
++pathParser;
|
||||
}
|
||||
return hash_value;
|
||||
@@ -2097,7 +2112,7 @@ namespace AZStd
|
||||
template <typename StringType>
|
||||
struct hash<AZ::IO::BasicPath<StringType>>
|
||||
{
|
||||
constexpr size_t operator()(const AZ::IO::BasicPath<StringType>& pathToHash) noexcept
|
||||
const size_t operator()(const AZ::IO::BasicPath<StringType>& pathToHash) noexcept
|
||||
{
|
||||
return AZStd::hash<AZ::IO::PathView>{}(pathToHash);
|
||||
}
|
||||
@@ -2108,11 +2123,11 @@ namespace AZStd
|
||||
template struct hash<AZ::IO::FixedMaxPath>;
|
||||
}
|
||||
|
||||
// Explicit instantations of our support Path classes
|
||||
// Explicit instantiations of our support Path classes
|
||||
namespace AZ::IO
|
||||
{
|
||||
// PathView hash
|
||||
constexpr size_t hash_value(const PathView& pathToHash) noexcept
|
||||
inline size_t hash_value(const PathView& pathToHash) noexcept
|
||||
{
|
||||
return AZStd::hash<PathView>{}(pathToHash);
|
||||
}
|
||||
|
||||
@@ -182,6 +182,36 @@ namespace UnitTest
|
||||
AZStd::tuple<AZStd::string_view, AZStd::string_view>(R"(foO/Bar)", "foo/bar")
|
||||
));
|
||||
|
||||
using PathHashParamFixture = PathParamFixture;
|
||||
TEST_P(PathHashParamFixture, HashOperator_HashesCaseInsensitiveForWindowsPaths)
|
||||
{
|
||||
AZ::IO::Path path1{ AZStd::get<0>(GetParam()), AZ::IO::WindowsPathSeparator };
|
||||
AZ::IO::Path path2{ AZStd::get<1>(GetParam()), AZ::IO::WindowsPathSeparator };
|
||||
size_t path1Hash = AZStd::hash<AZ::IO::PathView>{}(path1);
|
||||
size_t path2Hash = AZStd::hash<AZ::IO::PathView>{}(path2);
|
||||
EXPECT_EQ(path1Hash, path2Hash) << AZStd::string::format(R"(path1 "%s" should hash to path2 "%s"\n)",
|
||||
path1.c_str(), path2.c_str()).c_str();
|
||||
}
|
||||
|
||||
TEST_P(PathHashParamFixture, HashOperator_HashesCaseSensitiveForPosixPaths)
|
||||
{
|
||||
AZ::IO::Path path1{ AZStd::get<0>(GetParam()), AZ::IO::PosixPathSeparator };
|
||||
AZ::IO::Path path2{ AZStd::get<1>(GetParam()), AZ::IO::PosixPathSeparator };
|
||||
size_t path1Hash = AZStd::hash<AZ::IO::PathView>{}(path1);
|
||||
size_t path2Hash = AZStd::hash<AZ::IO::PathView>{}(path2);
|
||||
EXPECT_NE(path1Hash, path2Hash) << AZStd::string::format(R"(path1 "%s" should NOT hash to path2 "%s"\n)",
|
||||
path1.c_str(), path2.c_str()).c_str();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
HashPaths,
|
||||
PathHashParamFixture,
|
||||
::testing::Values(
|
||||
AZStd::tuple<AZStd::string_view, AZStd::string_view>("C:/test/foo", R"(c:\test/foo)"),
|
||||
AZStd::tuple<AZStd::string_view, AZStd::string_view>(R"(D:\test/bar/baz//foo)", "d:/test/bar/baz\\\\\\foo"),
|
||||
AZStd::tuple<AZStd::string_view, AZStd::string_view>(R"(foO/Bar)", "foo/bar")
|
||||
));
|
||||
|
||||
class PathSingleParamFixture
|
||||
: public ScopedAllocatorSetupFixture
|
||||
, public ::testing::WithParamInterface<AZStd::tuple<AZStd::string_view>>
|
||||
|
||||
+2
-2
@@ -206,12 +206,12 @@ namespace AzToolsFramework
|
||||
|
||||
const AZStd::string& AssetBrowserEntry::GetRelativePath() const
|
||||
{
|
||||
return m_relativePath;
|
||||
return m_relativePath.Native();
|
||||
}
|
||||
|
||||
const AZStd::string& AssetBrowserEntry::GetFullPath() const
|
||||
{
|
||||
return m_fullPath;
|
||||
return m_fullPath.Native();
|
||||
}
|
||||
|
||||
const AssetBrowserEntry* AssetBrowserEntry::GetChild(int index) const
|
||||
|
||||
+3
-2
@@ -9,6 +9,7 @@
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Math/Uuid.h>
|
||||
|
||||
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
|
||||
@@ -130,8 +131,8 @@ namespace AzToolsFramework
|
||||
protected:
|
||||
AZStd::string m_name;
|
||||
QString m_displayName;
|
||||
AZStd::string m_relativePath;
|
||||
AZStd::string m_fullPath;
|
||||
AZ::IO::Path m_relativePath;
|
||||
AZ::IO::Path m_fullPath;
|
||||
AZStd::vector<AssetBrowserEntry*> m_children;
|
||||
AssetBrowserEntry* m_parentAssetEntry = nullptr;
|
||||
|
||||
|
||||
+2
-2
@@ -37,8 +37,8 @@ namespace AzToolsFramework
|
||||
|
||||
void FolderAssetBrowserEntry::UpdateChildPaths(AssetBrowserEntry* child) const
|
||||
{
|
||||
child->m_relativePath = m_relativePath + AZ_CORRECT_DATABASE_SEPARATOR + child->m_name;
|
||||
child->m_fullPath = m_fullPath + AZ_CORRECT_DATABASE_SEPARATOR + child->m_name;
|
||||
child->m_relativePath = m_relativePath / child->m_name;
|
||||
child->m_fullPath = m_fullPath / child->m_name;
|
||||
AssetBrowserEntry::UpdateChildPaths(child);
|
||||
}
|
||||
|
||||
|
||||
+90
-113
@@ -10,6 +10,7 @@
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
|
||||
#include <AzToolsFramework/AssetBrowser/Entries/RootAssetBrowserEntry.h>
|
||||
#include <AzToolsFramework/AssetBrowser/Entries/FolderAssetBrowserEntry.h>
|
||||
@@ -24,8 +25,6 @@ namespace AzToolsFramework
|
||||
{
|
||||
namespace AssetBrowser
|
||||
{
|
||||
const char* GEMS_FOLDER_NAME = "Gems";
|
||||
|
||||
RootAssetBrowserEntry::RootAssetBrowserEntry()
|
||||
: AssetBrowserEntry()
|
||||
{
|
||||
@@ -53,13 +52,7 @@ namespace AzToolsFramework
|
||||
EntryCache::GetInstance()->Clear();
|
||||
|
||||
m_enginePath = enginePath;
|
||||
|
||||
// there is no "Gems" scan folder registered in db, create one manually
|
||||
auto gemFolder = aznew FolderAssetBrowserEntry();
|
||||
gemFolder->m_name = m_enginePath + AZ_CORRECT_DATABASE_SEPARATOR + GEMS_FOLDER_NAME;
|
||||
gemFolder->m_displayName = GEMS_FOLDER_NAME;
|
||||
gemFolder->m_isGemsFolder = true;
|
||||
AddChild(gemFolder);
|
||||
m_fullPath = enginePath;
|
||||
}
|
||||
|
||||
bool RootAssetBrowserEntry::IsInitialUpdate() const
|
||||
@@ -80,8 +73,17 @@ namespace AzToolsFramework
|
||||
|
||||
if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(scanFolderDatabaseEntry.m_scanFolder.c_str()))
|
||||
{
|
||||
const auto scanFolder = CreateFolders(scanFolderDatabaseEntry.m_scanFolder.c_str(), this);
|
||||
scanFolder->m_displayName = QString::fromUtf8(scanFolderDatabaseEntry.m_displayName.c_str());
|
||||
const auto scanFolder = CreateFolders(scanFolderDatabaseEntry.m_scanFolder, this);
|
||||
// Append an "[External]" to the display if the Scan Folder is NOT relative to the Engine Root path
|
||||
if (!AZ::IO::PathView(scanFolderDatabaseEntry.m_scanFolder).IsRelativeTo(m_enginePath))
|
||||
{
|
||||
scanFolder->m_displayName += " [External]";
|
||||
}
|
||||
else
|
||||
{
|
||||
scanFolder->m_displayName = QString::fromUtf8(scanFolderDatabaseEntry.m_displayName.c_str());
|
||||
}
|
||||
|
||||
EntryCache::GetInstance()->m_scanFolderIdMap[scanFolderDatabaseEntry.m_scanFolderID] = scanFolder;
|
||||
}
|
||||
}
|
||||
@@ -121,38 +123,34 @@ namespace AzToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
const char* filePath = fileDatabaseEntry.m_fileName.c_str();
|
||||
AZ::IO::FixedMaxPath absoluteFilePath = AZ::IO::FixedMaxPath(AZStd::string_view{ scanFolder->GetFullPath() })
|
||||
/ fileDatabaseEntry.m_fileName.c_str();
|
||||
|
||||
AssetBrowserEntry* file;
|
||||
// file can be either folder or actual file
|
||||
if (fileDatabaseEntry.m_isFolder)
|
||||
{
|
||||
file = CreateFolders(filePath, scanFolder);
|
||||
file = CreateFolders(absoluteFilePath.Native(), scanFolder);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZStd::string sourcePath;
|
||||
AZStd::string sourceName;
|
||||
AZStd::string sourceExtension;
|
||||
StringFunc::Path::Split(filePath, nullptr, &sourcePath, &sourceName, &sourceExtension);
|
||||
// if missing create folders leading to file's location and get immediate parent
|
||||
// (we don't need to have fileIds for any folders created yet, they will be added later)
|
||||
auto parent = CreateFolders(sourcePath.c_str(), scanFolder);
|
||||
auto parent = CreateFolders(absoluteFilePath.ParentPath().Native(), scanFolder);
|
||||
// for simplicity in AB, files are represented as sources, but they are missing SourceDatabaseEntry-specific information such as SourceUuid
|
||||
auto source = aznew SourceAssetBrowserEntry();
|
||||
source->m_name = (sourceName + sourceExtension).c_str();
|
||||
source->m_name = absoluteFilePath.Filename().Native();
|
||||
source->m_fileId = fileDatabaseEntry.m_fileID;
|
||||
source->m_displayName = QString::fromUtf8(source->m_name.c_str());
|
||||
source->m_scanFolderId = fileDatabaseEntry.m_scanFolderPK;
|
||||
source->m_extension = sourceExtension.c_str();
|
||||
source->m_extension = absoluteFilePath.Extension().Native();
|
||||
parent->AddChild(source);
|
||||
file = source;
|
||||
}
|
||||
|
||||
EntryCache::GetInstance()->m_fileIdMap[fileDatabaseEntry.m_fileID] = file;
|
||||
AZStd::string fullPath = file->m_fullPath;
|
||||
AzFramework::StringFunc::Path::Normalize(fullPath);
|
||||
EntryCache::GetInstance()->m_absolutePathToFileId[fullPath] = fileDatabaseEntry.m_fileID;
|
||||
AZStd::string filePath = AZ::IO::PathView(file->m_fullPath).LexicallyNormal().String();
|
||||
EntryCache::GetInstance()->m_absolutePathToFileId[filePath] = fileDatabaseEntry.m_fileID;
|
||||
}
|
||||
|
||||
bool RootAssetBrowserEntry::RemoveFile(const AZ::s64& fileId) const
|
||||
@@ -305,116 +303,95 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
FolderAssetBrowserEntry* RootAssetBrowserEntry::CreateFolder(const char* folderName, AssetBrowserEntry* parent)
|
||||
AssetBrowserEntry* RootAssetBrowserEntry::GetNearestAncestor(AZ::IO::PathView absolutePathView, AssetBrowserEntry* parent,
|
||||
AZStd::unordered_set<AssetBrowserEntry*>& visitedSet)
|
||||
{
|
||||
auto IsPathRelativeToEntry = [absolutePathView](AssetBrowserEntry* assetBrowserEntry)
|
||||
{
|
||||
auto& childPath = assetBrowserEntry->m_fullPath;
|
||||
return absolutePathView.IsRelativeTo(AZ::IO::PathView(childPath));
|
||||
};
|
||||
|
||||
if (visitedSet.contains(parent))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
visitedSet.insert(parent);
|
||||
|
||||
AssetBrowserEntry* nearestAncestor{};
|
||||
for (AssetBrowserEntry* childBrowserEntry : parent->m_children)
|
||||
{
|
||||
if (IsPathRelativeToEntry(childBrowserEntry))
|
||||
{
|
||||
// Walk the AssetBrowserEntry Tree looking for a nearer ancestor to the absolute path
|
||||
// If one is not found in the recursive call to GetNearestAncestor, then the childBrowserEntry
|
||||
// is the current best candidate
|
||||
AssetBrowserEntry* candidateAncestor = GetNearestAncestor(absolutePathView, childBrowserEntry, visitedSet);
|
||||
candidateAncestor = candidateAncestor != nullptr ? candidateAncestor : childBrowserEntry;
|
||||
AZ::IO::PathView candidatePathView(candidateAncestor->m_fullPath);
|
||||
// If the candidate is relative to the current nearest ancestor, then it is even nearer to the path
|
||||
if (!nearestAncestor || candidatePathView.IsRelativeTo(nearestAncestor->m_fullPath))
|
||||
{
|
||||
nearestAncestor = candidateAncestor;
|
||||
// If the full path compares equal to the AssetBrowserEntry path, then no need to proceed any further
|
||||
if (AZ::IO::PathView(nearestAncestor->m_fullPath) == absolutePathView)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nearestAncestor;
|
||||
}
|
||||
|
||||
FolderAssetBrowserEntry* RootAssetBrowserEntry::CreateFolder(AZStd::string_view folderName, AssetBrowserEntry* parent)
|
||||
{
|
||||
auto it = AZStd::find_if(parent->m_children.begin(), parent->m_children.end(), [folderName](AssetBrowserEntry* entry)
|
||||
{
|
||||
if (!azrtti_istypeof<FolderAssetBrowserEntry*>(entry))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return AzFramework::StringFunc::Equal(entry->m_name.c_str(), folderName);
|
||||
});
|
||||
{
|
||||
if (!azrtti_istypeof<FolderAssetBrowserEntry*>(entry))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return AZ::IO::PathView(entry->m_name) == AZ::IO::PathView(folderName);
|
||||
});
|
||||
if (it != parent->m_children.end())
|
||||
{
|
||||
return azrtti_cast<FolderAssetBrowserEntry*>(*it);
|
||||
}
|
||||
const auto folder = aznew FolderAssetBrowserEntry();
|
||||
folder->m_name = folderName;
|
||||
folder->m_displayName = folderName;
|
||||
folder->m_displayName = QString::fromUtf8(folderName.data(), aznumeric_caster(folderName.size()));
|
||||
parent->AddChild(folder);
|
||||
return folder;
|
||||
}
|
||||
|
||||
AssetBrowserEntry* RootAssetBrowserEntry::CreateFolders(const char* relativePath, AssetBrowserEntry* parent)
|
||||
AssetBrowserEntry* RootAssetBrowserEntry::CreateFolders(AZStd::string_view absolutePath, AssetBrowserEntry* parent)
|
||||
{
|
||||
auto children(parent->m_children);
|
||||
int n = 0;
|
||||
AZ::IO::PathView absolutePathView(absolutePath);
|
||||
// Find the nearest ancestor path to the absolutePath
|
||||
AZStd::unordered_set<AssetBrowserEntry*> visitedSet;
|
||||
|
||||
// check if folder with the same name already exists
|
||||
// step through every character in relativePath and compare to each child's relative path of suggested parent
|
||||
// if a character @n in child's rel path mismatches character at n in relativePath, remove that child from further search
|
||||
while (!children.empty() && relativePath[n])
|
||||
if (AssetBrowserEntry* nearestAncestor = GetNearestAncestor(absolutePathView, parent, visitedSet);
|
||||
nearestAncestor != nullptr)
|
||||
{
|
||||
AZStd::vector<AssetBrowserEntry*> toRemove;
|
||||
for (auto child : children)
|
||||
{
|
||||
auto& childPath = azrtti_istypeof<RootAssetBrowserEntry*>(parent) ? child->m_fullPath : child->m_relativePath;
|
||||
|
||||
// child's path mismatched, remove it from search candidates
|
||||
if (childPath.length() == n || childPath[n] != relativePath[n])
|
||||
{
|
||||
toRemove.push_back(child);
|
||||
|
||||
// it is possible that child may be a closer parent, substitute it as new potential parent
|
||||
// e.g. child->m_relativePath = 'Gems', relativePath = 'Gems/Assets', old parent = root, new parent = Gems
|
||||
if (childPath.length() == n && relativePath[n] == AZ_CORRECT_DATABASE_SEPARATOR)
|
||||
{
|
||||
parent = child;
|
||||
relativePath += n; // advance relative path n characters since the parent has changed
|
||||
n = 0; // Once the relative path pointer is advanced, reset n
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto entry : toRemove)
|
||||
{
|
||||
children.erase(AZStd::remove(children.begin(), children.end(), entry), children.end());
|
||||
}
|
||||
n++;
|
||||
parent = nearestAncestor;
|
||||
}
|
||||
|
||||
// filter out the remaining children that don't end with '/' or '\0'
|
||||
// for example if folderName = "foo", while children may still remain with names like "foo123",
|
||||
// which is not the same folder
|
||||
AZStd::vector<AssetBrowserEntry*> toRemove;
|
||||
for (auto child : children)
|
||||
// If the nearest ancestor is the absolutePath, then it is already crated
|
||||
if (absolutePathView == AZ::IO::PathView(parent->GetFullPath()))
|
||||
{
|
||||
auto& childPath = azrtti_istypeof<RootAssetBrowserEntry*>(parent) ? child->m_fullPath : child->m_relativePath;
|
||||
// check if there are non-null characters remaining @n
|
||||
if (childPath.length() > n)
|
||||
{
|
||||
toRemove.push_back(child);
|
||||
}
|
||||
}
|
||||
for (auto entry : toRemove)
|
||||
{
|
||||
children.erase(AZStd::remove(children.begin(), children.end(), entry), children.end());
|
||||
return parent;
|
||||
}
|
||||
|
||||
// at least one child remains, this means the folder with this name already exists, return it
|
||||
if (!children.empty())
|
||||
// create all missing folders
|
||||
auto proximateToPath = absolutePathView.IsRelativeTo(parent->m_fullPath)
|
||||
? absolutePathView.LexicallyProximate(parent->m_fullPath)
|
||||
: AZ::IO::FixedMaxPath(absolutePathView);
|
||||
for (AZ::IO::FixedMaxPath scanFolderSegment : proximateToPath)
|
||||
{
|
||||
parent = children.front();
|
||||
}
|
||||
// if it's a scanfolder, then do not create folders leading to it
|
||||
// e.g. instead of 'C:\dev\SampleProject' just create 'SampleProject'
|
||||
else if (parent->GetEntryType() == AssetEntryType::Root)
|
||||
{
|
||||
AZStd::string folderName;
|
||||
AzFramework::StringFunc::Path::Split(relativePath, nullptr, nullptr, &folderName);
|
||||
parent = CreateFolder(folderName.c_str(), parent);
|
||||
parent->m_fullPath = relativePath;
|
||||
}
|
||||
// otherwise create all missing folders
|
||||
else
|
||||
{
|
||||
n = 0;
|
||||
AZStd::string folderName(strlen(relativePath) + 1, '\0');
|
||||
// iterate through relativePath until the first '/'
|
||||
while (relativePath[n] && relativePath[n] != AZ_CORRECT_DATABASE_SEPARATOR)
|
||||
{
|
||||
folderName[n] = relativePath[n];
|
||||
n++;
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
parent = CreateFolder(folderName.c_str(), parent);
|
||||
}
|
||||
// n+1 also skips the '/' character
|
||||
if (relativePath[n] && relativePath[n + 1])
|
||||
{
|
||||
parent = CreateFolders(relativePath + n + 1, parent);
|
||||
}
|
||||
parent = CreateFolder(scanFolderSegment.c_str(), parent);
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
@@ -422,7 +399,7 @@ namespace AzToolsFramework
|
||||
void RootAssetBrowserEntry::UpdateChildPaths(AssetBrowserEntry* child) const
|
||||
{
|
||||
child->m_relativePath = child->m_name;
|
||||
child->m_fullPath = child->m_name;
|
||||
child->m_fullPath = m_fullPath / child->m_name;
|
||||
AssetBrowserEntry::UpdateChildPaths(child);
|
||||
}
|
||||
|
||||
|
||||
+8
-4
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Math/Uuid.h>
|
||||
|
||||
#include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntry.h>
|
||||
@@ -77,12 +78,15 @@ namespace AzToolsFramework
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(RootAssetBrowserEntry);
|
||||
|
||||
AZStd::string m_enginePath;
|
||||
AZ::IO::Path m_enginePath;
|
||||
|
||||
//! Create folder entry child
|
||||
FolderAssetBrowserEntry* CreateFolder(const char* folderName, AssetBrowserEntry* parent);
|
||||
//! Recursively create folder structure leading to relative path from parent
|
||||
AssetBrowserEntry* CreateFolders(const char* relativePath, AssetBrowserEntry* parent);
|
||||
FolderAssetBrowserEntry* CreateFolder(AZStd::string_view folderName, AssetBrowserEntry* parent);
|
||||
//! Recursively create folder structure leading to path from parent
|
||||
AssetBrowserEntry* CreateFolders(AZStd::string_view absolutePath, AssetBrowserEntry* parent);
|
||||
// Retrieves the nearest ancestor AssetBrowserEntry from the absolutePath
|
||||
static AssetBrowserEntry* GetNearestAncestor(AZ::IO::PathView absolutePath, AssetBrowserEntry* parent,
|
||||
AZStd::unordered_set<AssetBrowserEntry*>& visitedSet);
|
||||
|
||||
bool m_isInitialUpdate = false;
|
||||
};
|
||||
|
||||
+1
-3
@@ -26,9 +26,7 @@ namespace AzToolsFramework
|
||||
if (EntryCache* cache = EntryCache::GetInstance())
|
||||
{
|
||||
cache->m_fileIdMap.erase(m_fileId);
|
||||
AZStd::string fullPath = m_fullPath;
|
||||
AzFramework::StringFunc::Path::Normalize(fullPath);
|
||||
cache->m_absolutePathToFileId.erase(fullPath);
|
||||
cache->m_absolutePathToFileId.erase(m_fullPath.LexicallyNormal().Native());
|
||||
|
||||
if (m_sourceId != -1)
|
||||
{
|
||||
|
||||
@@ -40,9 +40,9 @@ foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJEC
|
||||
add_custom_target(${project_name}.Assets
|
||||
COMMENT "Processing ${project_name} assets..."
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-DLY_LOCK_FILE=$<TARGET_FILE_DIR:AZ::AssetProcessorBatch>/project_assets.lock
|
||||
-DLY_LOCK_FILE=$<GENEX_EVAL:$<TARGET_FILE_DIR:AZ::AssetProcessorBatch>>/project_assets.lock
|
||||
-P ${LY_ROOT_FOLDER}/cmake/CommandExecution.cmake
|
||||
EXEC_COMMAND $<TARGET_FILE:AZ::AssetProcessorBatch>
|
||||
EXEC_COMMAND $<GENEX_EVAL:$<TARGET_FILE:AZ::AssetProcessorBatch>>
|
||||
--zeroAnalysisMode
|
||||
--project-path=${project_real_path}
|
||||
--platforms=${LY_ASSET_DEPLOY_ASSET_TYPE}
|
||||
|
||||
@@ -38,8 +38,19 @@ ly_add_target(
|
||||
string(REPLACE "." ";" version_list "${LY_VERSION_STRING}")
|
||||
list(GET version_list 0 EXE_VERSION_INFO_0)
|
||||
list(GET version_list 1 EXE_VERSION_INFO_1)
|
||||
list(GET version_list 2 EXE_VERSION_INFO_2)
|
||||
list(GET version_list 3 EXE_VERSION_INFO_3)
|
||||
|
||||
list(LENGTH version_list version_component_count)
|
||||
if(${version_component_count} GREATER_EQUAL 3)
|
||||
list(GET version_list 2 EXE_VERSION_INFO_2)
|
||||
else()
|
||||
set(EXE_VERSION_INFO_2 0)
|
||||
endif()
|
||||
|
||||
if(${version_component_count} GREATER_EQUAL 4)
|
||||
list(GET version_list 3 EXE_VERSION_INFO_3)
|
||||
else()
|
||||
set(EXE_VERSION_INFO_3 0)
|
||||
endif()
|
||||
|
||||
ly_add_source_properties(
|
||||
SOURCES Shared/CrashHandler.cpp
|
||||
|
||||
@@ -8,4 +8,6 @@
|
||||
set(FILES
|
||||
Python_linux.cpp
|
||||
ProjectBuilderWorker_linux.cpp
|
||||
ProjectUtils_linux.cpp
|
||||
ProjectManagerDefs_linux.cpp
|
||||
)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ProjectManagerDefs.h>
|
||||
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
const QString ProjectBuildPathPostfix = ProjectBuildDirectoryName + "/linux";
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ProjectUtils.h>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
namespace ProjectUtils
|
||||
{
|
||||
AZ::Outcome<void, QString> FindSupportedCompilerForPlatform()
|
||||
{
|
||||
// Compiler detection not supported on platform
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
} // namespace ProjectUtils
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -8,4 +8,6 @@
|
||||
set(FILES
|
||||
Python_mac.cpp
|
||||
ProjectBuilderWorker_mac.cpp
|
||||
ProjectUtils_mac.cpp
|
||||
ProjectManagerDefs_mac.cpp
|
||||
)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ProjectManagerDefs.h>
|
||||
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
const QString ProjectBuildPathPostfix = ProjectBuildDirectoryName + "/mac_xcode";
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ProjectUtils.h>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
namespace ProjectUtils
|
||||
{
|
||||
AZ::Outcome<void, QString> FindSupportedCompilerForPlatform()
|
||||
{
|
||||
// Compiler detection not supported on platform
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
} // namespace ProjectUtils
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -8,4 +8,6 @@
|
||||
set(FILES
|
||||
Python_windows.cpp
|
||||
ProjectBuilderWorker_windows.cpp
|
||||
ProjectUtils_windows.cpp
|
||||
ProjectManagerDefs_windows.cpp
|
||||
)
|
||||
|
||||
@@ -75,8 +75,17 @@ namespace O3DE::ProjectManager
|
||||
|
||||
m_configProjectProcess->start(
|
||||
"cmake",
|
||||
QStringList{ "-B", QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix), "-S", m_projectInfo.m_path, "-G",
|
||||
"Visual Studio 16", "-DLY_3RDPARTY_PATH=" + engineInfo.m_thirdPartyPath });
|
||||
QStringList
|
||||
{
|
||||
"-B",
|
||||
QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix),
|
||||
"-S",
|
||||
m_projectInfo.m_path,
|
||||
"-G",
|
||||
"Visual Studio 16",
|
||||
"-DLY_3RDPARTY_PATH=" + engineInfo.m_thirdPartyPath,
|
||||
"-DLY_UNITY_BUILD=1"
|
||||
});
|
||||
|
||||
if (!m_configProjectProcess->waitForStarted())
|
||||
{
|
||||
@@ -124,8 +133,16 @@ namespace O3DE::ProjectManager
|
||||
|
||||
m_buildProjectProcess->start(
|
||||
"cmake",
|
||||
QStringList{ "--build", QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix), "--target",
|
||||
m_projectInfo.m_projectName + ".GameLauncher", "Editor", "--config", "profile" });
|
||||
QStringList
|
||||
{
|
||||
"--build",
|
||||
QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix),
|
||||
"--target",
|
||||
m_projectInfo.m_projectName + ".GameLauncher",
|
||||
"Editor",
|
||||
"--config",
|
||||
"profile"
|
||||
});
|
||||
|
||||
if (!m_buildProjectProcess->waitForStarted())
|
||||
{
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ProjectManagerDefs.h>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
const QString ProjectBuildPathPostfix = ProjectBuildDirectoryName + "/windows_vs2019";
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ProjectUtils.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
namespace ProjectUtils
|
||||
{
|
||||
AZ::Outcome<void, QString> FindSupportedCompilerForPlatform()
|
||||
{
|
||||
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
||||
QString programFilesPath = environment.value("ProgramFiles(x86)");
|
||||
QString vsWherePath = QDir(programFilesPath).filePath("Microsoft Visual Studio/Installer/vswhere.exe");
|
||||
|
||||
QFileInfo vsWhereFile(vsWherePath);
|
||||
if (vsWhereFile.exists() && vsWhereFile.isFile())
|
||||
{
|
||||
QProcess vsWhereProcess;
|
||||
vsWhereProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
vsWhereProcess.start(
|
||||
vsWherePath,
|
||||
QStringList{
|
||||
"-version",
|
||||
"16.0",
|
||||
"-latest",
|
||||
"-requires",
|
||||
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"-property",
|
||||
"isComplete"
|
||||
});
|
||||
|
||||
if (vsWhereProcess.waitForStarted() && vsWhereProcess.waitForFinished())
|
||||
{
|
||||
QString vsWhereOutput(vsWhereProcess.readAllStandardOutput());
|
||||
if (vsWhereOutput.startsWith("1"))
|
||||
{
|
||||
return AZ::Success();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AZ::Failure(QObject::tr("Visual Studio 2019 not found.\n\n"
|
||||
"Visual Studio 2019 is required to build this project."
|
||||
" Install any edition of <a href='https://visualstudio.microsoft.com/downloads/'>Visual Studio 2019</a>"
|
||||
" before proceeding to the next step."));
|
||||
}
|
||||
|
||||
} // namespace ProjectUtils
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <ScreenHeaderWidget.h>
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemCatalogScreen.h>
|
||||
#include <ProjectUtils.h>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHBoxLayout>
|
||||
@@ -222,38 +223,42 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void CreateProjectCtrl::CreateProject()
|
||||
{
|
||||
if (m_newProjectSettingsScreen->Validate())
|
||||
if (ProjectUtils::FindSupportedCompiler(this))
|
||||
{
|
||||
ProjectInfo projectInfo = m_newProjectSettingsScreen->GetProjectInfo();
|
||||
QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath();
|
||||
|
||||
auto result = PythonBindingsInterface::Get()->CreateProject(projectTemplatePath, projectInfo);
|
||||
if (result.IsSuccess())
|
||||
if (m_newProjectSettingsScreen->Validate())
|
||||
{
|
||||
// automatically register the project
|
||||
PythonBindingsInterface::Get()->AddProject(projectInfo.m_path);
|
||||
ProjectInfo projectInfo = m_newProjectSettingsScreen->GetProjectInfo();
|
||||
QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath();
|
||||
|
||||
auto result = PythonBindingsInterface::Get()->CreateProject(projectTemplatePath, projectInfo);
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
// automatically register the project
|
||||
PythonBindingsInterface::Get()->AddProject(projectInfo.m_path);
|
||||
|
||||
#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
if (!m_gemCatalogScreen->EnableDisableGemsForProject(projectInfo.m_path))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to configure gems"), tr("Failed to configure gems for template."));
|
||||
return;
|
||||
}
|
||||
if (!m_gemCatalogScreen->EnableDisableGemsForProject(projectInfo.m_path))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to configure gems"), tr("Failed to configure gems for template."));
|
||||
return;
|
||||
}
|
||||
#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
|
||||
|
||||
projectInfo.m_needsBuild = true;
|
||||
emit NotifyBuildProject(projectInfo);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
projectInfo.m_needsBuild = true;
|
||||
emit NotifyBuildProject(projectInfo);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, tr("Project creation failed"), tr("Failed to create project."));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, tr("Project creation failed"), tr("Failed to create project."));
|
||||
QMessageBox::warning(
|
||||
this, tr("Invalid project settings"), tr("Please correct the indicated project settings and try again."));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::warning(this, tr("Invalid project settings"), tr("Please correct the indicated project settings and try again."));
|
||||
}
|
||||
}
|
||||
|
||||
void CreateProjectCtrl::ReinitGemCatalogForSelectedTemplate()
|
||||
|
||||
@@ -70,8 +70,9 @@ namespace O3DE::ProjectManager
|
||||
m_lastProgress = progress;
|
||||
if (m_projectButton)
|
||||
{
|
||||
m_projectButton->SetButtonOverlayText(QString("%1 (%2%)\n\n").arg(tr("Building Project..."), QString::number(progress)));
|
||||
m_projectButton->SetButtonOverlayText(QString("%1 (%2%)<br>%3<br>").arg(tr("Building Project..."), QString::number(progress), tr("Click to <a href=\"logs\">view logs</a>.")));
|
||||
m_projectButton->SetProgressBarValue(progress);
|
||||
m_projectButton->SetBuildLogsLink(m_worker->GetLogFilePath());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
const QString ProjectBuilderWorker::BuildCancelled = QObject::tr("Build Cancelled.");
|
||||
|
||||
ProjectBuilderWorker::ProjectBuilderWorker(const ProjectInfo& projectInfo)
|
||||
: QObject()
|
||||
, m_projectInfo(projectInfo)
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace O3DE::ProjectManager
|
||||
// QProcess::waitForFinished uses -1 to indicate that the process should not timeout
|
||||
static constexpr int MaxBuildTimeMSecs = -1;
|
||||
// Build was cancelled
|
||||
static const QString BuildCancelled;
|
||||
inline static const QString BuildCancelled = QObject::tr("Build Cancelled.");
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ namespace O3DE::ProjectManager
|
||||
m_overlayLabel->setObjectName("labelButtonOverlay");
|
||||
m_overlayLabel->setWordWrap(true);
|
||||
m_overlayLabel->setAlignment(Qt::AlignCenter);
|
||||
m_overlayLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
|
||||
m_overlayLabel->setVisible(false);
|
||||
connect(m_overlayLabel, &QLabel::linkActivated, this, &LabelButton::OnLinkActivated);
|
||||
vLayout->addWidget(m_overlayLabel);
|
||||
|
||||
m_buildOverlayLayout = new QVBoxLayout();
|
||||
@@ -231,7 +233,7 @@ namespace O3DE::ProjectManager
|
||||
AzQtComponents::ShowFileOnDesktop(m_projectInfo.m_path);
|
||||
});
|
||||
menu->addSeparator();
|
||||
menu->addAction(tr("Duplicate"), this, [this]() { emit CopyProject(m_projectInfo.m_path); });
|
||||
menu->addAction(tr("Duplicate"), this, [this]() { emit CopyProject(m_projectInfo); });
|
||||
menu->addSeparator();
|
||||
menu->addAction(tr("Remove from O3DE"), this, [this]() { emit RemoveProject(m_projectInfo.m_path); });
|
||||
menu->addAction(tr("Delete this Project"), this, [this]() { emit DeleteProject(m_projectInfo.m_path); });
|
||||
@@ -266,6 +268,11 @@ namespace O3DE::ProjectManager
|
||||
SetProjectButtonAction(tr("Build Project"), [this]() { emit BuildProject(m_projectInfo); });
|
||||
}
|
||||
|
||||
void ProjectButton::SetBuildLogsLink(const QUrl& logUrl)
|
||||
{
|
||||
m_projectImageLabel->SetLogUrl(logUrl);
|
||||
}
|
||||
|
||||
void ProjectButton::ShowBuildFailed(bool show, const QUrl& logUrl)
|
||||
{
|
||||
if (!logUrl.isEmpty())
|
||||
|
||||
@@ -77,6 +77,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void SetProjectButtonAction(const QString& text, AZStd::function<void()> lambda);
|
||||
void SetProjectBuildButtonAction();
|
||||
void SetBuildLogsLink(const QUrl& logUrl);
|
||||
void ShowBuildFailed(bool show, const QUrl& logUrl);
|
||||
|
||||
void SetLaunchButtonEnabled(bool enabled);
|
||||
@@ -87,7 +88,7 @@ namespace O3DE::ProjectManager
|
||||
signals:
|
||||
void OpenProject(const QString& projectName);
|
||||
void EditProject(const QString& projectName);
|
||||
void CopyProject(const QString& projectName);
|
||||
void CopyProject(const ProjectInfo& projectInfo);
|
||||
void RemoveProject(const QString& projectName);
|
||||
void DeleteProject(const QString& projectName);
|
||||
void BuildProject(const ProjectInfo& projectInfo);
|
||||
|
||||
@@ -14,8 +14,10 @@ namespace O3DE::ProjectManager
|
||||
inline constexpr static int ProjectPreviewImageHeight = 280;
|
||||
inline constexpr static int ProjectTemplateImageWidth = 92;
|
||||
|
||||
static const QString ProjectBuildPathPostfix = "build/windows_vs2019";
|
||||
static const QString ProjectBuildDirectoryName = "build";
|
||||
extern const QString ProjectBuildPathPostfix;
|
||||
static const QString ProjectBuildPathCmakeFiles = "CMakeFiles";
|
||||
static const QString ProjectBuildErrorLogName = "CMakeProjectBuildError.log";
|
||||
static const QString ProjectCacheDirectoryName = "Cache";
|
||||
static const QString ProjectPreviewImagePath = "preview.png";
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include <ProjectUtils.h>
|
||||
#include <ProjectManagerDefs.h>
|
||||
#include <PythonBindingsInterface.h>
|
||||
|
||||
#include <QFileDialog>
|
||||
@@ -17,6 +18,8 @@
|
||||
#include <QProcessEnvironment>
|
||||
#include <QGuiApplication>
|
||||
#include <QProgressDialog>
|
||||
#include <QSpacerItem>
|
||||
#include <QGridLayout>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -57,29 +60,63 @@ namespace O3DE::ProjectManager
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool SkipFilePaths(const QString& curPath, QStringList& skippedPaths, QStringList& deeperSkippedPaths)
|
||||
{
|
||||
bool skip = false;
|
||||
for (const QString& skippedPath : skippedPaths)
|
||||
{
|
||||
QString nativeSkippedPath = QDir::toNativeSeparators(skippedPath);
|
||||
QString firstSectionSkippedPath = nativeSkippedPath.section(QDir::separator(), 0, 0);
|
||||
if (curPath == firstSectionSkippedPath)
|
||||
{
|
||||
// We are at the end of the path to skip, so skip it
|
||||
if (nativeSkippedPath == firstSectionSkippedPath)
|
||||
{
|
||||
skippedPaths.removeAll(skippedPath);
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
// Append the next section of the skipped path
|
||||
else
|
||||
{
|
||||
deeperSkippedPaths.append(nativeSkippedPath.section(QDir::separator(), 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return skip;
|
||||
}
|
||||
|
||||
typedef AZStd::function<void(/*fileCount=*/int, /*totalSizeInBytes=*/int)> StatusFunction;
|
||||
static void RecursiveGetAllFiles(const QDir& directory, QStringList& outFileList, qint64& outTotalSizeInBytes, StatusFunction statusCallback)
|
||||
static void RecursiveGetAllFiles(const QDir& directory, QStringList& skippedPaths, int& outFileCount, qint64& outTotalSizeInBytes, StatusFunction statusCallback)
|
||||
{
|
||||
const QStringList entries = directory.entryList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot);
|
||||
for (const QString& entryPath : entries)
|
||||
{
|
||||
const QString filePath = QDir::toNativeSeparators(QString("%1/%2").arg(directory.path()).arg(entryPath));
|
||||
|
||||
QStringList deeperSkippedPaths;
|
||||
if (SkipFilePaths(entryPath, skippedPaths, deeperSkippedPaths))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QFileInfo fileInfo(filePath);
|
||||
|
||||
if (fileInfo.isDir())
|
||||
{
|
||||
QDir subDirectory(filePath);
|
||||
RecursiveGetAllFiles(subDirectory, outFileList, outTotalSizeInBytes, statusCallback);
|
||||
RecursiveGetAllFiles(subDirectory, deeperSkippedPaths, outFileCount, outTotalSizeInBytes, statusCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
outFileList.push_back(filePath);
|
||||
++outFileCount;
|
||||
outTotalSizeInBytes += fileInfo.size();
|
||||
|
||||
const int updateStatusEvery = 64;
|
||||
if (outFileList.size() % updateStatusEvery == 0)
|
||||
if (outFileCount % updateStatusEvery == 0)
|
||||
{
|
||||
statusCallback(outFileList.size(), outTotalSizeInBytes);
|
||||
statusCallback(outFileCount, outTotalSizeInBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,7 +125,8 @@ namespace O3DE::ProjectManager
|
||||
static bool CopyDirectory(QProgressDialog* progressDialog,
|
||||
const QString& origPath,
|
||||
const QString& newPath,
|
||||
QStringList& filesToCopy,
|
||||
QStringList& skippedPaths,
|
||||
int filesToCopyCount,
|
||||
int& outNumCopiedFiles,
|
||||
qint64 totalSizeToCopy,
|
||||
qint64& outCopiedFileSize,
|
||||
@@ -100,18 +138,24 @@ namespace O3DE::ProjectManager
|
||||
return false;
|
||||
}
|
||||
|
||||
for (QString directory : original.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
|
||||
for (const QString& directory : original.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
|
||||
{
|
||||
if (progressDialog->wasCanceled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList deeperSkippedPaths;
|
||||
if (SkipFilePaths(directory, skippedPaths, deeperSkippedPaths))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString newDirectoryPath = newPath + QDir::separator() + directory;
|
||||
original.mkpath(newDirectoryPath);
|
||||
|
||||
if (!CopyDirectory(progressDialog, origPath + QDir::separator() + directory,
|
||||
newDirectoryPath, filesToCopy, outNumCopiedFiles, totalSizeToCopy, outCopiedFileSize, showIgnoreFileDialog))
|
||||
if (!CopyDirectory(progressDialog, origPath + QDir::separator() + directory, newDirectoryPath, deeperSkippedPaths,
|
||||
filesToCopyCount, outNumCopiedFiles, totalSizeToCopy, outCopiedFileSize, showIgnoreFileDialog))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -119,18 +163,25 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QLocale locale;
|
||||
const float progressDialogRangeHalf = qFabs(progressDialog->maximum() - progressDialog->minimum()) * 0.5f;
|
||||
for (QString file : original.entryList(QDir::Files))
|
||||
for (const QString& file : original.entryList(QDir::Files))
|
||||
{
|
||||
if (progressDialog->wasCanceled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unused by this function but neccesary to pass in to SkipFilePaths
|
||||
QStringList deeperSkippedPaths;
|
||||
if (SkipFilePaths(file, skippedPaths, deeperSkippedPaths))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Progress window update
|
||||
{
|
||||
// Weight in the number of already copied files as well as the copied bytes to get a better progress indication
|
||||
// for cases combining many small files and some really large files.
|
||||
const float normalizedNumFiles = static_cast<float>(outNumCopiedFiles) / filesToCopy.count();
|
||||
const float normalizedNumFiles = static_cast<float>(outNumCopiedFiles) / filesToCopyCount;
|
||||
const float normalizedFileSize = static_cast<float>(outCopiedFileSize) / totalSizeToCopy;
|
||||
const int progress = normalizedNumFiles * progressDialogRangeHalf + normalizedFileSize * progressDialogRangeHalf;
|
||||
progressDialog->setValue(progress);
|
||||
@@ -138,7 +189,7 @@ namespace O3DE::ProjectManager
|
||||
const QString copiedFileSizeString = locale.formattedDataSize(outCopiedFileSize);
|
||||
const QString totalFileSizeString = locale.formattedDataSize(totalSizeToCopy);
|
||||
progressDialog->setLabelText(QString("Coping file %1 of %2 (%3 of %4) ...").arg(QString::number(outNumCopiedFiles),
|
||||
QString::number(filesToCopy.count()),
|
||||
QString::number(filesToCopyCount),
|
||||
copiedFileSizeString,
|
||||
totalFileSizeString));
|
||||
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
@@ -192,6 +243,39 @@ namespace O3DE::ProjectManager
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ClearProjectBuildArtifactsAndCache(const QString& origPath, const QString& newPath, QWidget* parent)
|
||||
{
|
||||
QDir buildDirectory = QDir(newPath);
|
||||
if ((!buildDirectory.cd(ProjectBuildDirectoryName) || !DeleteProjectFiles(buildDirectory.path(), true))
|
||||
&& QDir(origPath).cd(ProjectBuildDirectoryName))
|
||||
{
|
||||
QMessageBox::warning(
|
||||
parent,
|
||||
QObject::tr("Clear Build Artifacts"),
|
||||
QObject::tr("Build artifacts failed to delete for moved project. Please manually delete build directory at \"%1\"")
|
||||
.arg(buildDirectory.path()),
|
||||
QMessageBox::Close);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir cacheDirectory = QDir(newPath);
|
||||
if ((!cacheDirectory.cd(ProjectCacheDirectoryName) || !DeleteProjectFiles(cacheDirectory.path(), true))
|
||||
&& QDir(origPath).cd(ProjectCacheDirectoryName))
|
||||
{
|
||||
QMessageBox::warning(
|
||||
parent,
|
||||
QObject::tr("Clear Asset Cache"),
|
||||
QObject::tr("Asset cache failed to delete for moved project. Please manually delete cache directory at \"%1\"")
|
||||
.arg(cacheDirectory.path()),
|
||||
QMessageBox::Close);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AddProjectDialog(QWidget* parent)
|
||||
{
|
||||
QString path = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(parent, QObject::tr("Select Project Directory")));
|
||||
@@ -213,7 +297,7 @@ namespace O3DE::ProjectManager
|
||||
return PythonBindingsInterface::Get()->RemoveProject(path);
|
||||
}
|
||||
|
||||
bool CopyProjectDialog(const QString& origPath, QWidget* parent)
|
||||
bool CopyProjectDialog(const QString& origPath, ProjectInfo& newProjectInfo, QWidget* parent)
|
||||
{
|
||||
bool copyResult = false;
|
||||
|
||||
@@ -223,6 +307,8 @@ namespace O3DE::ProjectManager
|
||||
QFileDialog::getExistingDirectory(parent, QObject::tr("Select New Project Directory"), parentOrigDir.path()));
|
||||
if (!newPath.isEmpty())
|
||||
{
|
||||
newProjectInfo.m_path = newPath;
|
||||
|
||||
if (!WarnDirectoryOverwrite(newPath, parent))
|
||||
{
|
||||
return false;
|
||||
@@ -234,7 +320,7 @@ namespace O3DE::ProjectManager
|
||||
return copyResult;
|
||||
}
|
||||
|
||||
bool CopyProject(const QString& origPath, const QString& newPath, QWidget* parent)
|
||||
bool CopyProject(const QString& origPath, const QString& newPath, QWidget* parent, bool skipRegister)
|
||||
{
|
||||
// Disallow copying from or into subdirectory
|
||||
if (IsDirectoryDescedent(origPath, newPath) || IsDirectoryDescedent(newPath, origPath))
|
||||
@@ -242,8 +328,13 @@ namespace O3DE::ProjectManager
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList filesToCopy;
|
||||
int filesToCopyCount = 0;
|
||||
qint64 totalSizeInBytes = 0;
|
||||
QStringList skippedPaths
|
||||
{
|
||||
ProjectBuildDirectoryName,
|
||||
ProjectCacheDirectoryName
|
||||
};
|
||||
|
||||
QProgressDialog* progressDialog = new QProgressDialog(parent);
|
||||
progressDialog->setAutoClose(true);
|
||||
@@ -254,7 +345,8 @@ namespace O3DE::ProjectManager
|
||||
progressDialog->show();
|
||||
|
||||
QLocale locale;
|
||||
RecursiveGetAllFiles(origPath, filesToCopy, totalSizeInBytes, [=](int fileCount, int sizeInBytes)
|
||||
QStringList getFilesSkippedPaths(skippedPaths);
|
||||
RecursiveGetAllFiles(origPath, getFilesSkippedPaths, filesToCopyCount, totalSizeInBytes, [=](int fileCount, int sizeInBytes)
|
||||
{
|
||||
// Create a human-readable version of the file size.
|
||||
const QString fileSizeString = locale.formattedDataSize(sizeInBytes);
|
||||
@@ -273,8 +365,10 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Phase 1: Copy files
|
||||
bool showIgnoreFileDialog = true;
|
||||
bool success = CopyDirectory(progressDialog, origPath, newPath, filesToCopy, numFilesCopied, totalSizeInBytes, copiedFileSize, showIgnoreFileDialog);
|
||||
if (success)
|
||||
QStringList copyFilesSkippedPaths(skippedPaths);
|
||||
bool success = CopyDirectory(progressDialog, origPath, newPath, copyFilesSkippedPaths, filesToCopyCount, numFilesCopied,
|
||||
totalSizeInBytes, copiedFileSize, showIgnoreFileDialog);
|
||||
if (success && !skipRegister)
|
||||
{
|
||||
// Phase 2: Register project
|
||||
success = RegisterProject(newPath);
|
||||
@@ -297,7 +391,7 @@ namespace O3DE::ProjectManager
|
||||
QDir projectDirectory(path);
|
||||
if (projectDirectory.exists())
|
||||
{
|
||||
// Check if there is an actual project hereor just force it
|
||||
// Check if there is an actual project here or just force it
|
||||
if (force || PythonBindingsInterface::Get()->GetProject(path).IsSuccess())
|
||||
{
|
||||
return projectDirectory.removeRecursively();
|
||||
@@ -307,12 +401,12 @@ namespace O3DE::ProjectManager
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveProject(QString origPath, QString newPath, QWidget* parent, bool ignoreRegister)
|
||||
bool MoveProject(QString origPath, QString newPath, QWidget* parent, bool skipRegister)
|
||||
{
|
||||
origPath = QDir::toNativeSeparators(origPath);
|
||||
newPath = QDir::toNativeSeparators(newPath);
|
||||
|
||||
if (!WarnDirectoryOverwrite(newPath, parent) || (!ignoreRegister && !UnregisterProject(origPath)))
|
||||
if (!WarnDirectoryOverwrite(newPath, parent) || (!skipRegister && !UnregisterProject(origPath)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -332,8 +426,13 @@ namespace O3DE::ProjectManager
|
||||
|
||||
DeleteProjectFiles(origPath, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If directoy rename succeeded then build and cache directories need to be deleted seperately
|
||||
ClearProjectBuildArtifactsAndCache(origPath, newPath, parent);
|
||||
}
|
||||
|
||||
if (!ignoreRegister && !RegisterProject(newPath))
|
||||
if (!skipRegister && !RegisterProject(newPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -374,46 +473,27 @@ namespace O3DE::ProjectManager
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsVS2019Installed_internal()
|
||||
bool FindSupportedCompiler(QWidget* parent)
|
||||
{
|
||||
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
||||
QString programFilesPath = environment.value("ProgramFiles(x86)");
|
||||
QString vsWherePath = programFilesPath + "\\Microsoft Visual Studio\\Installer\\vswhere.exe";
|
||||
auto findCompilerResult = FindSupportedCompilerForPlatform();
|
||||
|
||||
QFileInfo vsWhereFile(vsWherePath);
|
||||
if (vsWhereFile.exists() && vsWhereFile.isFile())
|
||||
if (!findCompilerResult.IsSuccess())
|
||||
{
|
||||
QProcess vsWhereProcess;
|
||||
vsWhereProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||
QMessageBox vsWarningMessage(parent);
|
||||
vsWarningMessage.setIcon(QMessageBox::Warning);
|
||||
vsWarningMessage.setWindowTitle(QObject::tr("Create Project"));
|
||||
// Makes link clickable
|
||||
vsWarningMessage.setTextFormat(Qt::RichText);
|
||||
vsWarningMessage.setText(findCompilerResult.GetError());
|
||||
vsWarningMessage.setStandardButtons(QMessageBox::Close);
|
||||
|
||||
vsWhereProcess.start(
|
||||
vsWherePath,
|
||||
QStringList{ "-version", "16.0", "-latest", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"-property", "isComplete" });
|
||||
|
||||
if (!vsWhereProcess.waitForStarted())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while (vsWhereProcess.waitForReadyRead())
|
||||
{
|
||||
}
|
||||
|
||||
QString vsWhereOutput(vsWhereProcess.readAllStandardOutput());
|
||||
if (vsWhereOutput.startsWith("1"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
QSpacerItem* horizontalSpacer = new QSpacerItem(600, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
|
||||
QGridLayout* layout = reinterpret_cast<QGridLayout*>(vsWarningMessage.layout());
|
||||
layout->addItem(horizontalSpacer, layout->rowCount(), 0, 1, layout->columnCount());
|
||||
vsWarningMessage.exec();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsVS2019Installed()
|
||||
{
|
||||
static bool vs2019Installed = IsVS2019Installed_internal();
|
||||
return vs2019Installed;
|
||||
return findCompilerResult.IsSuccess();
|
||||
}
|
||||
|
||||
ProjectManagerScreen GetProjectManagerScreen(const QString& screen)
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <ScreenDefs.h>
|
||||
#include <ProjectInfo.h>
|
||||
|
||||
#include <QWidget>
|
||||
#include <AzCore/Outcome/Outcome.h>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -16,14 +19,15 @@ namespace O3DE::ProjectManager
|
||||
bool AddProjectDialog(QWidget* parent = nullptr);
|
||||
bool RegisterProject(const QString& path);
|
||||
bool UnregisterProject(const QString& path);
|
||||
bool CopyProjectDialog(const QString& origPath, QWidget* parent = nullptr);
|
||||
bool CopyProject(const QString& origPath, const QString& newPath, QWidget* parent);
|
||||
bool CopyProjectDialog(const QString& origPath, ProjectInfo& newProjectInfo, QWidget* parent = nullptr);
|
||||
bool CopyProject(const QString& origPath, const QString& newPath, QWidget* parent, bool skipRegister = false);
|
||||
bool DeleteProjectFiles(const QString& path, bool force = false);
|
||||
bool MoveProject(QString origPath, QString newPath, QWidget* parent = nullptr, bool ignoreRegister = false);
|
||||
bool MoveProject(QString origPath, QString newPath, QWidget* parent, bool skipRegister = false);
|
||||
|
||||
bool ReplaceFile(const QString& origFile, const QString& newFile, QWidget* parent = nullptr, bool interactive = true);
|
||||
|
||||
bool IsVS2019Installed();
|
||||
bool FindSupportedCompiler(QWidget* parent = nullptr);
|
||||
AZ::Outcome<void, QString> FindSupportedCompilerForPlatform();
|
||||
|
||||
ProjectManagerScreen GetProjectManagerScreen(const QString& screen);
|
||||
} // namespace ProjectUtils
|
||||
|
||||
@@ -384,14 +384,17 @@ namespace O3DE::ProjectManager
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject);
|
||||
}
|
||||
}
|
||||
void ProjectsScreen::HandleCopyProject(const QString& projectPath)
|
||||
void ProjectsScreen::HandleCopyProject(const ProjectInfo& projectInfo)
|
||||
{
|
||||
if (!WarnIfInBuildQueue(projectPath))
|
||||
if (!WarnIfInBuildQueue(projectInfo.m_path))
|
||||
{
|
||||
ProjectInfo newProjectInfo(projectInfo);
|
||||
|
||||
// Open file dialog and choose location for copied project then register copy with O3DE
|
||||
if (ProjectUtils::CopyProjectDialog(projectPath, this))
|
||||
if (ProjectUtils::CopyProjectDialog(projectInfo.m_path, newProjectInfo, this))
|
||||
{
|
||||
ResetProjectsContent();
|
||||
emit NotifyBuildProject(newProjectInfo);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
}
|
||||
}
|
||||
@@ -516,7 +519,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
bool ProjectsScreen::StartProjectBuild(const ProjectInfo& projectInfo)
|
||||
{
|
||||
if (ProjectUtils::IsVS2019Installed())
|
||||
if (ProjectUtils::FindSupportedCompiler(this))
|
||||
{
|
||||
QMessageBox::StandardButton buildProject = QMessageBox::information(
|
||||
this,
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace O3DE::ProjectManager
|
||||
void HandleAddProjectButton();
|
||||
void HandleOpenProject(const QString& projectPath);
|
||||
void HandleEditProject(const QString& projectPath);
|
||||
void HandleCopyProject(const QString& projectPath);
|
||||
void HandleCopyProject(const ProjectInfo& projectInfo);
|
||||
void HandleRemoveProject(const QString& projectPath);
|
||||
void HandleDeleteProject(const QString& projectPath);
|
||||
|
||||
|
||||
@@ -219,11 +219,13 @@ namespace O3DE::ProjectManager
|
||||
// Move project first to avoid trying to update settings at the new location before it has been moved there
|
||||
if (newProjectSettings.m_path != m_projectInfo.m_path)
|
||||
{
|
||||
if (!ProjectUtils::MoveProject(m_projectInfo.m_path, newProjectSettings.m_path))
|
||||
if (!ProjectUtils::MoveProject(m_projectInfo.m_path, newProjectSettings.m_path, this))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Project move failed"), tr("Failed to move project."));
|
||||
return false;
|
||||
}
|
||||
|
||||
emit NotifyBuildProject(newProjectSettings);
|
||||
}
|
||||
|
||||
// Update project if settings changed
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <Application.h>
|
||||
#include <ProjectUtils.h>
|
||||
#include <ProjectManagerDefs.h>
|
||||
#include <ProjectManager_Test_Traits_Platform.h>
|
||||
|
||||
#include <QFile>
|
||||
@@ -25,16 +26,31 @@ namespace O3DE::ProjectManager
|
||||
: public ::UnitTest::ScopedAllocatorSetupFixture
|
||||
{
|
||||
public:
|
||||
static inline QString ReplaceFirstAWithB(const QString& originalString)
|
||||
{
|
||||
QString bString(originalString);
|
||||
return bString.replace(bString.indexOf('A'), 1, 'B');
|
||||
}
|
||||
|
||||
ProjectManagerUtilsTests()
|
||||
{
|
||||
m_application = AZStd::make_unique<ProjectManager::Application>();
|
||||
m_application->Init(false);
|
||||
|
||||
QDir dir;
|
||||
dir.mkdir("ProjectA");
|
||||
dir.mkdir("ProjectB");
|
||||
m_projectAPath = "ProjectA";
|
||||
|
||||
QFile origFile("ProjectA/origFile.txt");
|
||||
// Replaces first 'A' with 'B'
|
||||
m_projectBPath = ReplaceFirstAWithB(m_projectAPath);
|
||||
m_projectABuildPath = QString("%1%2%3").arg(m_projectAPath, QDir::separator(), ProjectBuildDirectoryName);
|
||||
m_projectBBuildPath = ReplaceFirstAWithB(m_projectABuildPath);
|
||||
|
||||
QDir dir;
|
||||
dir.mkpath(m_projectABuildPath);
|
||||
dir.mkdir(m_projectBPath);
|
||||
|
||||
m_projectAOrigFilePath = QString("%1%2%3").arg(m_projectAPath, QDir::separator(), "origFile.txt");
|
||||
m_projectBOrigFilePath = ReplaceFirstAWithB(m_projectAOrigFilePath);
|
||||
QFile origFile(m_projectAOrigFilePath);
|
||||
if (origFile.open(QIODevice::ReadWrite))
|
||||
{
|
||||
QTextStream stream(&origFile);
|
||||
@@ -42,63 +58,153 @@ namespace O3DE::ProjectManager
|
||||
origFile.close();
|
||||
}
|
||||
|
||||
QFile replaceFile("ProjectA/replaceFile.txt");
|
||||
m_projectAReplaceFilePath = QString("%1%2%3").arg(m_projectAPath, QDir::separator(), "replaceFile.txt");
|
||||
m_projectBReplaceFilePath = ReplaceFirstAWithB(m_projectAReplaceFilePath);
|
||||
QFile replaceFile(m_projectAReplaceFilePath);
|
||||
if (replaceFile.open(QIODevice::ReadWrite))
|
||||
{
|
||||
QTextStream stream(&replaceFile);
|
||||
stream << "replace" << Qt::endl;
|
||||
replaceFile.close();
|
||||
}
|
||||
|
||||
m_projectABuildFilePath = QString("%1%2%3").arg(m_projectABuildPath, QDir::separator(), "build.obj");
|
||||
m_projectBBuildFilePath = ReplaceFirstAWithB(m_projectABuildFilePath);
|
||||
QFile buildFile(m_projectABuildFilePath);
|
||||
if (buildFile.open(QIODevice::ReadWrite))
|
||||
{
|
||||
QTextStream stream(&buildFile);
|
||||
stream << "x0FFFFFFFF" << Qt::endl;
|
||||
buildFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
~ProjectManagerUtilsTests()
|
||||
{
|
||||
QDir dirA("ProjectA");
|
||||
QDir dirA(m_projectAPath);
|
||||
dirA.removeRecursively();
|
||||
|
||||
QDir dirB("ProjectB");
|
||||
QDir dirB(m_projectBPath);
|
||||
dirB.removeRecursively();
|
||||
|
||||
m_application.reset();
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<ProjectManager::Application> m_application;
|
||||
|
||||
QString m_projectAPath;
|
||||
QString m_projectAOrigFilePath;
|
||||
QString m_projectAReplaceFilePath;
|
||||
QString m_projectABuildPath;
|
||||
QString m_projectABuildFilePath;
|
||||
QString m_projectBPath;
|
||||
QString m_projectBOrigFilePath;
|
||||
QString m_projectBReplaceFilePath;
|
||||
QString m_projectBBuildPath;
|
||||
QString m_projectBBuildFilePath;
|
||||
|
||||
};
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
|
||||
TEST_F(ProjectManagerUtilsTests, DISABLED_MoveProject_Succeeds)
|
||||
TEST_F(ProjectManagerUtilsTests, DISABLED_MoveProject_MovesExpectedFiles)
|
||||
#else
|
||||
TEST_F(ProjectManagerUtilsTests, MoveProject_Succeeds)
|
||||
TEST_F(ProjectManagerUtilsTests, MoveProject_MovesExpectedFiles)
|
||||
#endif // !AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
|
||||
{
|
||||
EXPECT_TRUE(MoveProject(
|
||||
QDir::currentPath() + QDir::separator() + "ProjectA",
|
||||
QDir::currentPath() + QDir::separator() + "ProjectB",
|
||||
QDir::currentPath() + QDir::separator() + m_projectAPath,
|
||||
QDir::currentPath() + QDir::separator() + m_projectBPath,
|
||||
nullptr, true));
|
||||
|
||||
QFileInfo origFile("ProjectA/origFile.txt");
|
||||
EXPECT_TRUE(!origFile.exists());
|
||||
QFileInfo origFile(m_projectAOrigFilePath);
|
||||
EXPECT_FALSE(origFile.exists());
|
||||
|
||||
QFileInfo replaceFile("ProjectA/replaceFile.txt");
|
||||
EXPECT_TRUE(!replaceFile.exists());
|
||||
QFileInfo replaceFile(m_projectAReplaceFilePath);
|
||||
EXPECT_FALSE(replaceFile.exists());
|
||||
|
||||
QFileInfo origFileMoved("ProjectB/origFile.txt");
|
||||
QFileInfo origFileMoved(m_projectBOrigFilePath);
|
||||
EXPECT_TRUE(origFileMoved.exists() && origFileMoved.isFile());
|
||||
|
||||
QFileInfo replaceFileMoved("ProjectB/replaceFile.txt");
|
||||
QFileInfo replaceFileMoved(m_projectBReplaceFilePath);
|
||||
EXPECT_TRUE(replaceFileMoved.exists() && replaceFileMoved.isFile());
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
|
||||
TEST_F(ProjectManagerUtilsTests, DISABLED_MoveProject_DoesntMoveBuild)
|
||||
#else
|
||||
TEST_F(ProjectManagerUtilsTests, MoveProject_DoesntMoveBuild)
|
||||
#endif // !AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
|
||||
{
|
||||
EXPECT_TRUE(MoveProject(
|
||||
QDir::currentPath() + QDir::separator() + m_projectAPath,
|
||||
QDir::currentPath() + QDir::separator() + m_projectBPath,
|
||||
nullptr, true));
|
||||
|
||||
QFileInfo origFile(m_projectAOrigFilePath);
|
||||
EXPECT_FALSE(origFile.exists());
|
||||
|
||||
QFileInfo origFileMoved(m_projectBOrigFilePath);
|
||||
EXPECT_TRUE(origFileMoved.exists() && origFileMoved.isFile());
|
||||
|
||||
QDir buildDir(m_projectBBuildPath);
|
||||
EXPECT_FALSE(buildDir.exists());
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
|
||||
TEST_F(ProjectManagerUtilsTests, DISABLED_CopyProject_CopiesExpectedFiles)
|
||||
#else
|
||||
TEST_F(ProjectManagerUtilsTests, CopyProject_CopiesExpectedFiles)
|
||||
#endif // !AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
|
||||
{
|
||||
EXPECT_TRUE(CopyProject(
|
||||
QDir::currentPath() + QDir::separator() + m_projectAPath,
|
||||
QDir::currentPath() + QDir::separator() + m_projectBPath,
|
||||
nullptr, true));
|
||||
|
||||
QFileInfo origFile(m_projectAOrigFilePath);
|
||||
EXPECT_TRUE(origFile.exists());
|
||||
|
||||
QFileInfo replaceFile(m_projectAReplaceFilePath);
|
||||
EXPECT_TRUE(replaceFile.exists());
|
||||
|
||||
QFileInfo origFileMoved(m_projectBOrigFilePath);
|
||||
EXPECT_TRUE(origFileMoved.exists() && origFileMoved.isFile());
|
||||
|
||||
QFileInfo replaceFileMoved(m_projectBReplaceFilePath);
|
||||
EXPECT_TRUE(replaceFileMoved.exists() && replaceFileMoved.isFile());
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
|
||||
TEST_F(ProjectManagerUtilsTests, DISABLED_CopyProject_DoesntCopyBuild)
|
||||
#else
|
||||
TEST_F(ProjectManagerUtilsTests, CopyProject_DoesntCopyBuild)
|
||||
#endif // !AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
|
||||
{
|
||||
EXPECT_TRUE(CopyProject(
|
||||
QDir::currentPath() + QDir::separator() + m_projectAPath,
|
||||
QDir::currentPath() + QDir::separator() + m_projectBPath,
|
||||
nullptr, true));
|
||||
|
||||
QFileInfo origFile(m_projectAOrigFilePath);
|
||||
EXPECT_TRUE(origFile.exists());
|
||||
|
||||
QFileInfo origFileMoved(m_projectBOrigFilePath);
|
||||
EXPECT_TRUE(origFileMoved.exists() && origFileMoved.isFile());
|
||||
|
||||
QDir buildDir(m_projectBBuildPath);
|
||||
EXPECT_FALSE(buildDir.exists());
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
|
||||
TEST_F(ProjectManagerUtilsTests, DISABLED_ReplaceFile_Succeeds)
|
||||
#else
|
||||
TEST_F(ProjectManagerUtilsTests, ReplaceFile_Succeeds)
|
||||
#endif // !AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
|
||||
{
|
||||
EXPECT_TRUE(ReplaceFile("ProjectA/origFile.txt", "ProjectA/replaceFile.txt", nullptr, false));
|
||||
EXPECT_TRUE(ReplaceFile(m_projectAOrigFilePath, m_projectAReplaceFilePath, nullptr, false));
|
||||
|
||||
QFile origFile("ProjectA/origFile.txt");
|
||||
if (origFile.open(QIODevice::ReadOnly))
|
||||
QFile origFile(m_projectAOrigFilePath);
|
||||
EXPECT_TRUE(origFile.open(QIODevice::ReadOnly));
|
||||
{
|
||||
QTextStream stream(&origFile);
|
||||
QString line = stream.readLine();
|
||||
@@ -106,10 +212,6 @@ namespace O3DE::ProjectManager
|
||||
|
||||
origFile.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
} // namespace ProjectUtils
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"type": "Code",
|
||||
"summary": "The Asset Memory Analyzer Gem provides tools to profile asset memory usage in Open 3D Engine through ImGUI (Immediate Mode Graphical User Interface).",
|
||||
"canonical_tags": ["Gem"],
|
||||
"user_tags": ["Debug", "Utillity", "Tools"],
|
||||
"user_tags": ["Debug", "Utility", "Tools"],
|
||||
"icon_path": "preview.png",
|
||||
"requirements": ""
|
||||
}
|
||||
|
||||
@@ -14,11 +14,3 @@ add_subdirectory(RPI)
|
||||
add_subdirectory(Tools)
|
||||
add_subdirectory(Utils)
|
||||
|
||||
# The "Atom" Gem will alias the real Atom_AtomBridge target variants
|
||||
# allows the enabling and disabling the "Atom" Gem to build the pre-requisite dependencies
|
||||
ly_create_alias(NAME Atom.Clients NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Clients)
|
||||
ly_create_alias(NAME Atom.Servers NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Servers)
|
||||
if(PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
ly_create_alias(NAME Atom.Builders NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Builders)
|
||||
ly_create_alias(NAME Atom.Tools NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Tools)
|
||||
endif()
|
||||
|
||||
@@ -6,3 +6,7 @@
|
||||
#
|
||||
add_subdirectory(ReferenceMaterials)
|
||||
add_subdirectory(Sponza)
|
||||
|
||||
if(PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
ly_create_alias(NAME AtomContent.Builders NAMESPACE Gem TARGETS Gem::AtomContent_ReferenceMaterials.Builders Gem::AtomContent_Sponza.Builders)
|
||||
endif()
|
||||
|
||||
@@ -115,3 +115,18 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
ly_create_alias(NAME Atom_AtomBridge.Builders NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Editor)
|
||||
ly_create_alias(NAME Atom_AtomBridge.Tools NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Editor)
|
||||
endif()
|
||||
|
||||
# The "Atom" Gem will alias the real Atom_AtomBridge target variants
|
||||
# allows the enabling and disabling the "Atom" Gem to build the pre-requisite dependencies
|
||||
# The "AtomLyIntegration" Gem will also alias the real Atom_AtomBridge target variants
|
||||
# The Atom Gem does the same at the moment.
|
||||
ly_create_alias(NAME Atom.Clients NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Clients)
|
||||
ly_create_alias(NAME Atom.Servers NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Servers)
|
||||
ly_create_alias(NAME AtomLyIntegration.Clients NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Clients)
|
||||
ly_create_alias(NAME AtomLyIntegration.Servers NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Servers)
|
||||
if(PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
ly_create_alias(NAME Atom.Builders NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Builders)
|
||||
ly_create_alias(NAME Atom.Tools NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Tools)
|
||||
ly_create_alias(NAME AtomLyIntegration.Builders NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Builders)
|
||||
ly_create_alias(NAME AtomLyIntegration.Tools NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Tools)
|
||||
endif()
|
||||
|
||||
@@ -15,11 +15,3 @@ add_subdirectory(AtomBridge)
|
||||
add_subdirectory(AtomViewportDisplayInfo)
|
||||
add_subdirectory(AtomViewportDisplayIcons)
|
||||
|
||||
# The "AtomLyIntegration" Gem will also alias the real Atom_AtomBridge target variants
|
||||
# The Atom Gem does the same at the moment.
|
||||
ly_create_alias(NAME AtomLyIntegration.Clients NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Clients)
|
||||
ly_create_alias(NAME AtomLyIntegration.Servers NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Servers)
|
||||
if(PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
ly_create_alias(NAME AtomLyIntegration.Builders NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Builders)
|
||||
ly_create_alias(NAME AtomLyIntegration.Tools NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Tools)
|
||||
endif()
|
||||
|
||||
@@ -57,5 +57,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
PRIVATE
|
||||
AZ::AzCore
|
||||
Gem::EMotionFX_Atom.Static
|
||||
RUNTIME_DEPENDENCIES
|
||||
Gem::EMotionFX.Editor
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"type": "Code",
|
||||
"summary": "The Wwise Audio Engine Gem provides support for Audiokinetic Wave Works Interactive Sound Engine (Wwise).",
|
||||
"canonical_tags": ["Gem"],
|
||||
"user_tags": ["Audio", "Utiltity", "Tools"],
|
||||
"user_tags": ["Audio", "Utility", "Tools"],
|
||||
"icon_path": "preview.png",
|
||||
"requirements": "Users will need to download WWise from the AudioKinetic web site: https://www.audiokinetic.com/download/"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"type": "Code",
|
||||
"summary": "The Audio System Gem provides the Audio Translation Layer (ATL) and Audio Controls Editor, which add support for audio in Open 3D Engine.",
|
||||
"canonical_tags": ["Gem"],
|
||||
"user_tags": ["Audio", "Utiltity", "Tools"],
|
||||
"user_tags": ["Audio", "Utility", "Tools"],
|
||||
"icon_path": "preview.png",
|
||||
"requirements": ""
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ namespace EMotionFX
|
||||
|
||||
Group::ActorGroup* group = azrtti_cast<Group::ActorGroup*>(&target);
|
||||
group->SetName(AZ::SceneAPI::DataTypes::Utilities::CreateUniqueName<Group::IActorGroup>(scene.GetName(), scene.GetManifest()));
|
||||
group->SetBestMatchingRootBone(scene.GetGraph());
|
||||
|
||||
// LOD Rule need to be built first in the actor, so we know which mesh and bone belongs to LOD.
|
||||
// After this call, LOD rule will be populated with all the LOD bones
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
|
||||
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
|
||||
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
|
||||
#include <SceneAPI/SceneCore/DataTypes/Rules/IRule.h>
|
||||
#include <SceneAPI/SceneCore/DataTypes/GraphData/IBoneData.h>
|
||||
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
|
||||
#include <SceneAPI/SceneCore/DataTypes/Groups/ISceneNodeGroup.h>
|
||||
#include <SceneAPI/SceneData/GraphData/RootBoneData.h>
|
||||
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
|
||||
#include <SceneAPI/SceneData/Rules/CoordinateSystemRule.h>
|
||||
#include <SceneAPIExt/Groups/ActorGroup.h>
|
||||
@@ -76,6 +80,22 @@ namespace EMotionFX
|
||||
m_selectedRootBone = selectedRootBone;
|
||||
}
|
||||
|
||||
void ActorGroup::SetBestMatchingRootBone(const AZ::SceneAPI::Containers::SceneGraph& sceneGraph)
|
||||
{
|
||||
auto nameContentView = AZ::SceneAPI::Containers::Views::MakePairView(sceneGraph.GetNameStorage(), sceneGraph.GetContentStorage());
|
||||
auto graphDownwardsView = AZ::SceneAPI::Containers::Views::MakeSceneGraphDownwardsView<AZ::SceneAPI::Containers::Views::BreadthFirst>(
|
||||
sceneGraph, sceneGraph.GetRoot(), nameContentView.begin(), true);
|
||||
|
||||
for (auto it = graphDownwardsView.begin(); it != graphDownwardsView.end(); ++it)
|
||||
{
|
||||
if (it->second && it->second->RTTI_IsTypeOf(AZ::SceneData::GraphData::RootBoneData::TYPEINFO_Uuid()))
|
||||
{
|
||||
SetSelectedRootBone(it->first.GetPath());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActorGroup::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
|
||||
@@ -45,8 +45,8 @@ namespace EMotionFX
|
||||
|
||||
// IActorGroup overrides
|
||||
const AZStd::string& GetSelectedRootBone() const override;
|
||||
|
||||
void SetSelectedRootBone(const AZStd::string& selectedRootBone) override;
|
||||
void SetBestMatchingRootBone(const AZ::SceneAPI::Containers::SceneGraph& sceneGraph) override;
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
static bool IActorGroupVersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <SceneAPI/SceneCore/DataTypes/Groups/IGroup.h>
|
||||
|
||||
namespace AZ::SceneAPI::Containers
|
||||
{
|
||||
class SceneGraph;
|
||||
}
|
||||
|
||||
namespace EMotionFX
|
||||
{
|
||||
namespace Pipeline
|
||||
@@ -26,6 +31,7 @@ namespace EMotionFX
|
||||
|
||||
virtual const AZStd::string& GetSelectedRootBone() const = 0;
|
||||
virtual void SetSelectedRootBone(const AZStd::string& selectedRootBone) = 0;
|
||||
virtual void SetBestMatchingRootBone(const AZ::SceneAPI::Containers::SceneGraph& sceneGraph) = 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,25 +412,24 @@ namespace EMotionFX
|
||||
|
||||
void NonUniformMotionData::UpdateDuration()
|
||||
{
|
||||
m_duration = 0.0f;
|
||||
|
||||
for (const JointData& jointData : m_jointData)
|
||||
{
|
||||
if (!jointData.m_positionTrack.m_times.empty())
|
||||
{
|
||||
m_duration = jointData.m_positionTrack.m_times.back();
|
||||
return;
|
||||
m_duration = AZ::GetMax(m_duration, jointData.m_positionTrack.m_times.back());
|
||||
}
|
||||
|
||||
if (!jointData.m_rotationTrack.m_times.empty())
|
||||
{
|
||||
m_duration = jointData.m_rotationTrack.m_times.back();
|
||||
return;
|
||||
m_duration = AZ::GetMax(m_duration, jointData.m_rotationTrack.m_times.back());
|
||||
}
|
||||
|
||||
#ifndef EMFX_SCALE_DISABLED
|
||||
if (!jointData.m_scaleTrack.m_times.empty())
|
||||
{
|
||||
m_duration = jointData.m_scaleTrack.m_times.back();
|
||||
return;
|
||||
m_duration = AZ::GetMax(m_duration, jointData.m_scaleTrack.m_times.back());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -439,8 +438,7 @@ namespace EMotionFX
|
||||
{
|
||||
if (!morphData.m_track.m_times.empty())
|
||||
{
|
||||
m_duration = morphData.m_track.m_times.back();
|
||||
return;
|
||||
m_duration = AZ::GetMax(m_duration, morphData.m_track.m_times.back());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,12 +446,9 @@ namespace EMotionFX
|
||||
{
|
||||
if (!floatData.m_track.m_times.empty())
|
||||
{
|
||||
m_duration = floatData.m_track.m_times.back();
|
||||
return;
|
||||
m_duration = AZ::GetMax(m_duration, floatData.m_track.m_times.back());
|
||||
}
|
||||
}
|
||||
|
||||
m_duration = 0.0f;
|
||||
}
|
||||
|
||||
void NonUniformMotionData::AllocateJointPositionSamples(size_t jointDataIndex, size_t numSamples)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"type": "Code",
|
||||
"summary": "The Expression Evaluation Gem provides a method for parsing and executing string expressions in Open 3D Engine.",
|
||||
"canonical_tags": ["Gem"],
|
||||
"user_tags": ["Scripting", "Utiltity"],
|
||||
"user_tags": ["Scripting", "Utility"],
|
||||
"icon_path": "preview.png",
|
||||
"requirements": ""
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"type": "Code",
|
||||
"summary": "The Game State Samples Gem provides a set of sample game states (built on top of the Game State Gem), including primary user selection, main menu, level loading, level running, and level paused.",
|
||||
"canonical_tags": ["Gem"],
|
||||
"user_tags": ["Gameplay", "Samples", "Assets"],
|
||||
"user_tags": ["Gameplay", "Sample", "Assets"],
|
||||
"icon_path": "preview.png",
|
||||
"requirements": ""
|
||||
}
|
||||
|
||||
@@ -148,7 +148,6 @@ namespace LyShine
|
||||
{
|
||||
LyShineAllocatorScope::ActivateAllocators();
|
||||
|
||||
LyShineRequestBus::Handler::BusConnect();
|
||||
UiSystemBus::Handler::BusConnect();
|
||||
UiSystemToolsBus::Handler::BusConnect();
|
||||
UiFrameworkBus::Handler::BusConnect();
|
||||
@@ -196,7 +195,6 @@ namespace LyShine
|
||||
UiSystemBus::Handler::BusDisconnect();
|
||||
UiSystemToolsBus::Handler::BusDisconnect();
|
||||
UiFrameworkBus::Handler::BusDisconnect();
|
||||
LyShineRequestBus::Handler::BusDisconnect();
|
||||
CrySystemEventBus::Handler::BusDisconnect();
|
||||
|
||||
LyShineAllocatorScope::DeactivateAllocators();
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
#include <LmbrCentral/Rendering/MaterialAsset.h>
|
||||
|
||||
#include <LyShine/LyShineBus.h>
|
||||
#include <LyShine/Bus/UiSystemBus.h>
|
||||
#include <LyShine/Bus/UiCanvasManagerBus.h>
|
||||
#include <LyShine/Bus/Tools/UiSystemToolsBus.h>
|
||||
@@ -28,7 +27,6 @@ namespace LyShine
|
||||
|
||||
class LyShineSystemComponent
|
||||
: public AZ::Component
|
||||
, protected LyShineRequestBus::Handler
|
||||
, protected UiSystemBus::Handler
|
||||
, protected UiSystemToolsBus::Handler
|
||||
, protected LyShineAllocatorScope
|
||||
|
||||
@@ -577,7 +577,7 @@ namespace NvCloth
|
||||
const AZ::Vector3& renderTangent = renderTangents[renderVertexIndex];
|
||||
destTangentsBuffer[index].Set(
|
||||
renderTangent,
|
||||
1.0f);
|
||||
-1.0f); // Shader function ConstructTBN inverts w to change bitangent sign, but the bitangents passed are already corrected, so passing -1.0 to counteract.
|
||||
}
|
||||
|
||||
if (destBitangentsBuffer)
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace NvCloth
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const float Tolerance = 0.0001f;
|
||||
const float Tolerance = 1e-7f;
|
||||
}
|
||||
|
||||
bool TangentSpaceHelper::CalculateNormals(
|
||||
@@ -33,7 +33,8 @@ namespace NvCloth
|
||||
const size_t vertexCount = vertices.size();
|
||||
|
||||
// Reset results
|
||||
outNormals.resize(vertexCount, AZ::Vector3::CreateZero());
|
||||
outNormals.resize(vertexCount);
|
||||
AZStd::fill(outNormals.begin(), outNormals.end(), AZ::Vector3::CreateZero());
|
||||
|
||||
// calculate the normals per triangle
|
||||
for (size_t i = 0; i < triangleCount; ++i)
|
||||
@@ -114,8 +115,10 @@ namespace NvCloth
|
||||
const size_t vertexCount = vertices.size();
|
||||
|
||||
// Reset results
|
||||
outTangents.resize(vertexCount, AZ::Vector3::CreateZero());
|
||||
outBitangents.resize(vertexCount, AZ::Vector3::CreateZero());
|
||||
outTangents.resize(vertexCount);
|
||||
outBitangents.resize(vertexCount);
|
||||
AZStd::fill(outTangents.begin(), outTangents.end(), AZ::Vector3::CreateZero());
|
||||
AZStd::fill(outBitangents.begin(), outBitangents.end(), AZ::Vector3::CreateZero());
|
||||
|
||||
// calculate the base vectors per triangle
|
||||
for (size_t i = 0; i < triangleCount; ++i)
|
||||
@@ -192,9 +195,12 @@ namespace NvCloth
|
||||
const size_t vertexCount = vertices.size();
|
||||
|
||||
// Reset results
|
||||
outTangents.resize(vertexCount, AZ::Vector3::CreateZero());
|
||||
outBitangents.resize(vertexCount, AZ::Vector3::CreateZero());
|
||||
outNormals.resize(vertexCount, AZ::Vector3::CreateZero());
|
||||
outTangents.resize(vertexCount);
|
||||
outBitangents.resize(vertexCount);
|
||||
outNormals.resize(vertexCount);
|
||||
AZStd::fill(outTangents.begin(), outTangents.end(), AZ::Vector3::CreateZero());
|
||||
AZStd::fill(outBitangents.begin(), outBitangents.end(), AZ::Vector3::CreateZero());
|
||||
AZStd::fill(outNormals.begin(), outNormals.end(), AZ::Vector3::CreateZero());
|
||||
|
||||
// calculate the base vectors per triangle
|
||||
for (size_t i = 0; i < triangleCount; ++i)
|
||||
|
||||
@@ -124,6 +124,9 @@ namespace UnitTest
|
||||
const AZStd::vector<AZ::Vector4>& motionConstraints = clothConstraints->GetMotionConstraints();
|
||||
|
||||
EXPECT_TRUE(motionConstraints.size() == SimulationParticles.size());
|
||||
EXPECT_THAT(motionConstraints[0].GetAsVector3(), IsCloseTolerance(SimulationParticles[0].GetAsVector3(), Tolerance));
|
||||
EXPECT_THAT(motionConstraints[1].GetAsVector3(), IsCloseTolerance(SimulationParticles[1].GetAsVector3(), Tolerance));
|
||||
EXPECT_THAT(motionConstraints[2].GetAsVector3(), IsCloseTolerance(SimulationParticles[2].GetAsVector3(), Tolerance));
|
||||
EXPECT_NEAR(motionConstraints[0].GetW(), 6.0f, Tolerance);
|
||||
EXPECT_NEAR(motionConstraints[1].GetW(), 0.0f, Tolerance);
|
||||
EXPECT_NEAR(motionConstraints[2].GetW(), 0.0f, Tolerance);
|
||||
@@ -277,6 +280,9 @@ namespace UnitTest
|
||||
const AZStd::vector<AZ::Vector4>& separationConstraints = clothConstraints->GetSeparationConstraints();
|
||||
|
||||
EXPECT_TRUE(motionConstraints.size() == newParticles.size());
|
||||
EXPECT_THAT(motionConstraints[0].GetAsVector3(), IsCloseTolerance(newParticles[0].GetAsVector3(), Tolerance));
|
||||
EXPECT_THAT(motionConstraints[1].GetAsVector3(), IsCloseTolerance(newParticles[1].GetAsVector3(), Tolerance));
|
||||
EXPECT_THAT(motionConstraints[2].GetAsVector3(), IsCloseTolerance(newParticles[2].GetAsVector3(), Tolerance));
|
||||
EXPECT_NEAR(motionConstraints[0].GetW(), 3.0f, Tolerance);
|
||||
EXPECT_NEAR(motionConstraints[1].GetW(), 1.5f, Tolerance);
|
||||
EXPECT_NEAR(motionConstraints[2].GetW(), 0.0f, Tolerance);
|
||||
@@ -285,8 +291,8 @@ namespace UnitTest
|
||||
EXPECT_NEAR(separationConstraints[0].GetW(), 3.0f, Tolerance);
|
||||
EXPECT_NEAR(separationConstraints[1].GetW(), 1.5f, Tolerance);
|
||||
EXPECT_NEAR(separationConstraints[2].GetW(), 0.3f, Tolerance);
|
||||
EXPECT_THAT(separationConstraints[0].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-3.03902f, 2.80752f, 3.80752f), Tolerance));
|
||||
EXPECT_THAT(separationConstraints[1].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-1.41659f, 0.651243f, -0.348757f), Tolerance));
|
||||
EXPECT_THAT(separationConstraints[2].GetAsVector3(), IsCloseTolerance(AZ::Vector3(6.15313f, -0.876132f, 0.123868f), Tolerance));
|
||||
EXPECT_THAT(separationConstraints[0].GetAsVector3(), IsCloseTolerance(AZ::Vector3(0.0f, 3.53553f, 4.53553f), Tolerance));
|
||||
EXPECT_THAT(separationConstraints[1].GetAsVector3(), IsCloseTolerance(AZ::Vector3(0.0f, 2.06066f, 1.06066f), Tolerance));
|
||||
EXPECT_THAT(separationConstraints[2].GetAsVector3(), IsCloseTolerance(AZ::Vector3(1.0f, -3.74767f, -2.74767f), Tolerance));
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"type": "Tool",
|
||||
"summary": "The Script Canvas Gem provides Open 3D Engine's visual scripting environment, Script Canvas.",
|
||||
"canonical_tags": ["Gem"],
|
||||
"user_tags": ["Scripting", "Tools", "Utiltiy"],
|
||||
"user_tags": ["Scripting", "Tools", "Utility"],
|
||||
"icon_path": "preview.png",
|
||||
"requirements": ""
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"type": "Code",
|
||||
"summary": "The Surface Data Gem provides functionality to emit signals or tags from surfaces such as meshes and terrain.",
|
||||
"canonical_tags": ["Gem"],
|
||||
"user_tags": ["Environment", "Utiltiy", "Design"],
|
||||
"user_tags": ["Environment", "Utility", "Design"],
|
||||
"icon_path": "preview.png",
|
||||
"requirements": ""
|
||||
}
|
||||
|
||||
@@ -529,6 +529,12 @@ function(ly_force_download_package package_name)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf ${temp_download_target}
|
||||
WORKING_DIRECTORY ${final_folder} COMMAND_ECHO STDOUT OUTPUT_VARIABLE unpack_result)
|
||||
|
||||
# For the runtime dependencies cases, we need the timestamps of the files coming from 3rdParty to be newer than the ones
|
||||
# from the output so the new versions get copied over. The untar from the previous step preserves timestamps so they
|
||||
# can produce binaries with older timestamps to the ones that are in the build output.
|
||||
file(GLOB_RECURSE package_files LIST_DIRECTORIES false ${final_folder}/*)
|
||||
file(TOUCH_NOCREATE ${package_files})
|
||||
|
||||
if (NOT ${unpack_result} EQUAL 0)
|
||||
message(SEND_ERROR "ly_package: required package {package_name} could not be unpacked. Compile may fail! Enable LY_PACKAGE_DEBUG to debug.")
|
||||
return()
|
||||
|
||||
@@ -124,3 +124,42 @@ function(ly_file_read path content)
|
||||
set(${content} ${file_content} PARENT_SCOPE)
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${path})
|
||||
endfunction()
|
||||
|
||||
|
||||
#! ly_get_last_path_segment_concat_sha256 : Concatenates the last path segment of the absolute path
|
||||
# with the first 8 characters of the absolute path SHA256 hash to make a unique relative path segment
|
||||
function(ly_get_last_path_segment_concat_sha256 absolute_path output_path)
|
||||
string(SHA256 target_source_hash ${absolute_path})
|
||||
string(SUBSTRING ${target_source_hash} 0 8 target_source_hash)
|
||||
cmake_path(GET absolute_path FILENAME last_path_segment)
|
||||
cmake_path(SET last_path_segment_sha256_path "${last_path_segment}-${target_source_hash}")
|
||||
|
||||
set(${output_path} ${last_path_segment_sha256_path} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
#! ly_get_engine_relative_source_dir: Attempts to form a path relative to the BASE_DIRECTORY.
|
||||
# If that fails the last path segment of the absolute_target_source_dir concatenated with a SHA256 hash to form a target directory
|
||||
# \arg:BASE_DIRECTORY - Directory to base relative path against. Defaults to LY_ROOT_FOLDER
|
||||
function(ly_get_engine_relative_source_dir absolute_target_source_dir output_source_dir)
|
||||
|
||||
set(options)
|
||||
set(oneValueArgs BASE_DIRECTORY)
|
||||
set(multiValueArgs)
|
||||
cmake_parse_arguments(ly_get_engine_relative_source_dir "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
if(NOT ly_get_engine_relative_source_dir_BASE_DIRECTORY)
|
||||
set(ly_get_engine_relative_source_dir_BASE_DIRECTORY ${LY_ROOT_FOLDER})
|
||||
endif()
|
||||
|
||||
# Get a relative target source directory to the LY root folder if possible
|
||||
# Otherwise use the final component name
|
||||
cmake_path(IS_PREFIX LY_ROOT_FOLDER ${absolute_target_source_dir} is_target_source_dir_subdirectory_of_engine)
|
||||
if(is_target_source_dir_subdirectory_of_engine)
|
||||
cmake_path(RELATIVE_PATH absolute_target_source_dir BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE relative_target_source_dir)
|
||||
else()
|
||||
ly_get_last_path_segment_concat_sha256(${absolute_target_source_dir} target_source_dir_last_path_segment)
|
||||
unset(relative_target_source_dir)
|
||||
cmake_path(APPEND relative_target_source_dir "External" ${target_source_dir_last_path_segment})
|
||||
endif()
|
||||
|
||||
set(${output_source_dir} ${relative_target_source_dir} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
@@ -90,6 +90,13 @@ function(ly_create_alias)
|
||||
# Replace the CMake list separator with a space to replicate the space separated TARGETS arguments
|
||||
string(REPLACE ";" " " create_alias_args "${ly_create_alias_NAME},${ly_create_alias_NAMESPACE},${ly_create_alias_TARGETS}")
|
||||
set_property(DIRECTORY APPEND PROPERTY LY_CREATE_ALIAS_ARGUMENTS "${create_alias_args}")
|
||||
|
||||
# Store the directory path in the GLOBAL property so that it can be accessed
|
||||
# in the layout install logic. Skip if the directory has already been added
|
||||
get_property(ly_all_target_directories GLOBAL PROPERTY LY_ALL_TARGET_DIRECTORIES)
|
||||
if(NOT CMAKE_CURRENT_SOURCE_DIR IN_LIST ly_all_target_directories)
|
||||
set_property(GLOBAL APPEND PROPERTY LY_ALL_TARGET_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# ly_enable_gems
|
||||
|
||||
+10
-5
@@ -391,10 +391,10 @@ function(ly_delayed_target_link_libraries)
|
||||
endif()
|
||||
|
||||
if(item_type STREQUAL MODULE_LIBRARY)
|
||||
target_include_directories(${target} ${visibility} $<TARGET_PROPERTY:${item},INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
target_link_libraries(${target} ${visibility} $<TARGET_PROPERTY:${item},INTERFACE_LINK_LIBRARIES>)
|
||||
target_compile_definitions(${target} ${visibility} $<TARGET_PROPERTY:${item},INTERFACE_COMPILE_DEFINITIONS>)
|
||||
target_compile_options(${target} ${visibility} $<TARGET_PROPERTY:${item},INTERFACE_COMPILE_OPTIONS>)
|
||||
target_include_directories(${target} ${visibility} $<GENEX_EVAL:$<TARGET_PROPERTY:${item},INTERFACE_INCLUDE_DIRECTORIES>>)
|
||||
target_link_libraries(${target} ${visibility} $<GENEX_EVAL:$<TARGET_PROPERTY:${item},INTERFACE_LINK_LIBRARIES>>)
|
||||
target_compile_definitions(${target} ${visibility} $<GENEX_EVAL:$<TARGET_PROPERTY:${item},INTERFACE_COMPILE_DEFINITIONS>>)
|
||||
target_compile_options(${target} ${visibility} $<GENEX_EVAL:$<TARGET_PROPERTY:${item},INTERFACE_COMPILE_OPTIONS>>)
|
||||
else()
|
||||
ly_parse_third_party_dependencies(${item})
|
||||
target_link_libraries(${target} ${visibility} ${item})
|
||||
@@ -659,7 +659,12 @@ function(ly_get_vs_folder_directory absolute_target_source_dir output_source_dir
|
||||
if(is_target_prefix_of_engine_root)
|
||||
cmake_path(RELATIVE_PATH absolute_target_source_dir BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE relative_target_source_dir)
|
||||
else()
|
||||
cmake_path(GET absolute_target_source_dir RELATIVE_PART relative_target_source_dir)
|
||||
cmake_path(IS_PREFIX CMAKE_SOURCE_DIR ${absolute_target_source_dir} is_target_prefix_of_source_dir)
|
||||
if(is_target_prefix_of_source_dir)
|
||||
cmake_path(RELATIVE_PATH absolute_target_source_dir BASE_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE relative_target_source_dir)
|
||||
else()
|
||||
cmake_path(GET absolute_target_source_dir RELATIVE_PART relative_target_source_dir)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(${output_source_dir} ${relative_target_source_dir} PARENT_SCOPE)
|
||||
|
||||
+23
-28
@@ -125,37 +125,32 @@ install(FILES ${_cmake_package_dest}
|
||||
DESTINATION ./Tools/Redistributables/CMake
|
||||
)
|
||||
|
||||
# temporary workaround for acquiring the 3rd party SPDX license manifest, the desired location is from
|
||||
# another git repository that's private. once it's public, only how the URL is formed should change
|
||||
set(LY_INSTALLER_3RD_PARTY_LICENSE_URL "" CACHE STRING "URL to the 3rd party SPDX license manifest file for inclusion in packaging.")
|
||||
# the version string and git tags are intended to be synchronized so it should be safe to use that instead
|
||||
# of directly calling into git which could get messy in certain scenarios
|
||||
if(${CPACK_PACKAGE_VERSION} VERSION_GREATER "0.0.0.0")
|
||||
set(_3rd_party_license_filename NOTICES.txt)
|
||||
|
||||
if(${LY_VERSION_STRING} VERSION_GREATER "0.0.0.0" AND NOT LY_INSTALLER_3RD_PARTY_LICENSE_URL)
|
||||
message(FATAL_ERROR "Missing required URL for the 3rd party SPDX license manifest file. "
|
||||
"Please specifiy where to acquire the file via LY_INSTALLER_3RD_PARTY_LICENSE_URL")
|
||||
endif()
|
||||
set(_3rd_party_license_url "https://raw.githubusercontent.com/o3de/3p-package-source/${CPACK_PACKAGE_VERSION}/${_3rd_party_license_filename}")
|
||||
set(_3rd_party_license_dest ${CPACK_BINARY_DIR}/${_3rd_party_license_filename})
|
||||
|
||||
string(REPLACE "/" ";" _url_components ${LY_INSTALLER_3RD_PARTY_LICENSE_URL})
|
||||
list(POP_BACK _url_components _3rd_party_license_filename)
|
||||
|
||||
set(_3rd_party_license_dest ${CPACK_BINARY_DIR}/${_3rd_party_license_filename})
|
||||
|
||||
# use the plain file downloader as we don't have the file hash available and using a dummy will
|
||||
# delete the file once it fails hash verification
|
||||
file(DOWNLOAD
|
||||
${LY_INSTALLER_3RD_PARTY_LICENSE_URL}
|
||||
${_3rd_party_license_dest}
|
||||
STATUS _status
|
||||
TLS_VERIFY ON
|
||||
)
|
||||
list(POP_FRONT _status _status_code)
|
||||
|
||||
if (${_status_code} EQUAL 0 AND EXISTS ${_3rd_party_license_dest})
|
||||
install(FILES ${_3rd_party_license_dest}
|
||||
DESTINATION .
|
||||
# use the plain file downloader as we don't have the file hash available and using a dummy will
|
||||
# delete the file once it fails hash verification
|
||||
file(DOWNLOAD
|
||||
${_3rd_party_license_url}
|
||||
${_3rd_party_license_dest}
|
||||
STATUS _status
|
||||
TLS_VERIFY ON
|
||||
)
|
||||
else()
|
||||
file(REMOVE ${_3rd_party_license_dest})
|
||||
message(FATAL_ERROR "Failed to acquire the 3rd Party license manifest file. Error: ${_status}")
|
||||
list(POP_FRONT _status _status_code)
|
||||
|
||||
if (${_status_code} EQUAL 0 AND EXISTS ${_3rd_party_license_dest})
|
||||
install(FILES ${_3rd_party_license_dest}
|
||||
DESTINATION .
|
||||
)
|
||||
else()
|
||||
file(REMOVE ${_3rd_party_license_dest})
|
||||
message(FATAL_ERROR "Failed to acquire the 3rd Party license manifest file at ${_3rd_party_license_url}. Error: ${_status}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# checks for and removes trailing slash
|
||||
|
||||
@@ -9,6 +9,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
<PropertyGroup>
|
||||
<UseMultiToolTask>true</UseMultiToolTask>
|
||||
<EnforceProcessCountAcrossBuilds>true</EnforceProcessCountAcrossBuilds>
|
||||
@VCPKG_CONFIGURATION_MAPPING@
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
|
||||
@@ -5,8 +5,19 @@
|
||||
#
|
||||
#
|
||||
|
||||
include(cmake/FileUtil.cmake)
|
||||
|
||||
set(CMAKE_INSTALL_MESSAGE NEVER) # Simplify messages to reduce output noise
|
||||
|
||||
define_property(TARGET PROPERTY LY_INSTALL_GENERATE_RUN_TARGET
|
||||
BRIEF_DOCS "Defines if a \"RUN\" targets should be created when installing this target Gem"
|
||||
FULL_DOCS [[
|
||||
Property which is set on targets that should generate a "RUN"
|
||||
target when installed. This \"RUN\" target helps to run the
|
||||
binary from the installed location directly from the IDE.
|
||||
]]
|
||||
)
|
||||
|
||||
ly_set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME Core)
|
||||
|
||||
cmake_path(RELATIVE_PATH CMAKE_RUNTIME_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE runtime_output_directory)
|
||||
@@ -18,26 +29,6 @@ cmake_path(RELATIVE_PATH CMAKE_LIBRARY_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_B
|
||||
set(install_output_folder "\${CMAKE_INSTALL_PREFIX}/${runtime_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>")
|
||||
|
||||
|
||||
function(ly_get_engine_relative_source_dir absolute_target_source_dir output_source_dir)
|
||||
# Get a relative target source directory to the LY root folder if possible
|
||||
# Otherwise use the final component name
|
||||
cmake_path(IS_PREFIX LY_ROOT_FOLDER ${absolute_target_source_dir} is_target_prefix_of_engine_root)
|
||||
if(is_target_prefix_of_engine_root)
|
||||
cmake_path(RELATIVE_PATH absolute_target_source_dir BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE relative_target_source_dir)
|
||||
else()
|
||||
# In this case the target source directory is outside of the engine root of the target source directory and concatenate the first
|
||||
# is used first 8 characters of the absolute path SHA256 hash to make a unique relative directory
|
||||
# that can be used to install the generated CMakeLists.txt
|
||||
# of a SHA256 hash
|
||||
string(SHA256 target_source_hash ${absolute_target_source_dir})
|
||||
string(SUBSTRING ${target_source_hash} 0 8 target_source_hash)
|
||||
cmake_path(GET absolute_target_source_dir FILENAME target_source_dirname)
|
||||
cmake_path(SET relative_target_source_dir "${target_source_dirname}-${target_source_hash}")
|
||||
endif()
|
||||
|
||||
set(${output_source_dir} ${relative_target_source_dir} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
#! ly_setup_target: Setup the data needed to re-create the cmake target commands for a single target
|
||||
function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_target_source_dir)
|
||||
# De-alias target name
|
||||
@@ -82,6 +73,7 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar
|
||||
PATTERN *.h
|
||||
PATTERN *.hpp
|
||||
PATTERN *.inl
|
||||
PATTERN *.hxx
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
@@ -122,15 +114,19 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar
|
||||
set(NAMESPACE_PLACEHOLDER "")
|
||||
set(NAME_PLACEHOLDER ${TARGET_NAME})
|
||||
endif()
|
||||
get_target_property(should_create_helper ${TARGET_NAME} LY_INSTALL_GENERATE_RUN_TARGET)
|
||||
if(should_create_helper)
|
||||
set(NAME_PLACEHOLDER ${NAME_PLACEHOLDER}.Imported)
|
||||
endif()
|
||||
|
||||
set(TARGET_TYPE_PLACEHOLDER "")
|
||||
get_target_property(target_type ${NAME_PLACEHOLDER} TYPE)
|
||||
get_target_property(target_type ${TARGET_NAME} TYPE)
|
||||
# Remove the _LIBRARY since we dont need to pass that to ly_add_targets
|
||||
string(REPLACE "_LIBRARY" "" TARGET_TYPE_PLACEHOLDER ${target_type})
|
||||
# For HEADER_ONLY libs we end up generating "INTERFACE" libraries, need to specify HEADERONLY instead
|
||||
string(REPLACE "INTERFACE" "HEADERONLY" TARGET_TYPE_PLACEHOLDER ${TARGET_TYPE_PLACEHOLDER})
|
||||
if(TARGET_TYPE_PLACEHOLDER STREQUAL "MODULE")
|
||||
get_target_property(gem_module ${NAME_PLACEHOLDER} GEM_MODULE)
|
||||
get_target_property(gem_module ${TARGET_NAME} GEM_MODULE)
|
||||
if(gem_module)
|
||||
set(TARGET_TYPE_PLACEHOLDER "GEM_MODULE")
|
||||
endif()
|
||||
@@ -163,7 +159,6 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar
|
||||
unset(RUNTIME_DEPENDENCIES_PLACEHOLDER)
|
||||
endif()
|
||||
|
||||
|
||||
get_target_property(inteface_build_dependencies_props ${TARGET_NAME} INTERFACE_LINK_LIBRARIES)
|
||||
unset(INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER)
|
||||
if(inteface_build_dependencies_props)
|
||||
@@ -187,6 +182,23 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar
|
||||
list(REMOVE_DUPLICATES INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER)
|
||||
string(REPLACE ";" "\n" INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "${INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER}")
|
||||
|
||||
# If the target is an executable/application, add a custom target so we can debug the target in project-centric workflow
|
||||
if(should_create_helper)
|
||||
string(REPLACE ".Imported" "" RUN_TARGET_NAME ${NAME_PLACEHOLDER})
|
||||
set(target_types_with_debugging_helper EXECUTABLE APPLICATION)
|
||||
if(NOT target_type IN_LIST target_types_with_debugging_helper)
|
||||
message(FATAL_ERROR "Cannot generate a RUN target for ${TARGET_NAME}, type is ${target_type}")
|
||||
endif()
|
||||
set(TARGET_RUN_HELPER
|
||||
"add_custom_target(${RUN_TARGET_NAME})
|
||||
set_target_properties(${RUN_TARGET_NAME} PROPERTIES
|
||||
FOLDER \"CMakePredefinedTargets/SDK\"
|
||||
VS_DEBUGGER_COMMAND \$<GENEX_EVAL:\$<TARGET_PROPERTY:${NAME_PLACEHOLDER},IMPORTED_LOCATION>>
|
||||
VS_DEBUGGER_COMMAND_ARGUMENTS \"--project-path=\${LY_DEFAULT_PROJECT_PATH}\"
|
||||
)"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Config file
|
||||
set(target_file_contents "# Generated by O3DE install\n\n")
|
||||
if(NOT target_type STREQUAL INTERFACE_LIBRARY)
|
||||
@@ -199,13 +211,13 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar
|
||||
set(target_location "\${LY_ROOT_FOLDER}/${library_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>/${target_library_output_subdirectory}/$<TARGET_FILE_NAME:${TARGET_NAME}>")
|
||||
elseif(target_type STREQUAL SHARED_LIBRARY)
|
||||
string(APPEND target_file_contents
|
||||
"set_property(TARGET ${TARGET_NAME}
|
||||
"set_property(TARGET ${NAME_PLACEHOLDER}
|
||||
APPEND_STRING PROPERTY IMPORTED_IMPLIB
|
||||
$<$<CONFIG:$<CONFIG>$<ANGLE-R>:\"\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>/$<TARGET_LINKER_FILE_NAME:${TARGET_NAME}>\"$<ANGLE-R>
|
||||
)
|
||||
")
|
||||
string(APPEND target_file_contents
|
||||
"set_property(TARGET ${TARGET_NAME}
|
||||
"set_property(TARGET ${NAME_PLACEHOLDER}
|
||||
PROPERTY IMPORTED_IMPLIB_$<UPPER_CASE:$<CONFIG>>
|
||||
\"\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>/$<TARGET_LINKER_FILE_NAME:${TARGET_NAME}>\"
|
||||
)
|
||||
@@ -217,11 +229,11 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar
|
||||
|
||||
if(target_location)
|
||||
string(APPEND target_file_contents
|
||||
"set_property(TARGET ${TARGET_NAME}
|
||||
"set_property(TARGET ${NAME_PLACEHOLDER}
|
||||
APPEND_STRING PROPERTY IMPORTED_LOCATION
|
||||
$<$<CONFIG:$<CONFIG>$<ANGLE-R>:${target_location}$<ANGLE-R>
|
||||
)
|
||||
set_property(TARGET ${TARGET_NAME}
|
||||
set_property(TARGET ${NAME_PLACEHOLDER}
|
||||
PROPERTY IMPORTED_LOCATION_$<UPPER_CASE:$<CONFIG>>
|
||||
${target_location}
|
||||
)
|
||||
@@ -254,7 +266,6 @@ endfunction()
|
||||
|
||||
#! ly_setup_subdirectory: setup all targets in the subdirectory
|
||||
function(ly_setup_subdirectory absolute_target_source_dir)
|
||||
# Get the target source directory relative to the LY roo folder
|
||||
ly_get_engine_relative_source_dir(${absolute_target_source_dir} relative_target_source_dir)
|
||||
|
||||
# The builtin BUILDSYSTEM_TARGETS property isn't being used here as that returns the de-alised
|
||||
@@ -540,56 +551,74 @@ function(ly_setup_others)
|
||||
)
|
||||
# Exclude transient artifacts that shouldn't be copied to the install layout
|
||||
list(FILTER external_subdir_files EXCLUDE REGEX "/([Bb]uild|[Cc]ache|[Uu]ser)$")
|
||||
list(APPEND filtered_asset_paths ${external_subdir_files})
|
||||
# Storing a "mapping" of gem candidate directories, to external_subdirectory files using
|
||||
# a DIRECTORY property for the "value" and the GLOBAL property for the "key"
|
||||
set_property(DIRECTORY ${gem_candidate_dir} APPEND PROPERTY directory_filtered_asset_paths "${external_subdir_files}")
|
||||
set_property(GLOBAL APPEND PROPERTY global_gem_candidate_dirs_prop ${gem_candidate_dir})
|
||||
endforeach()
|
||||
|
||||
# At this point the filtered_assets_paths contains the list of all directories and files
|
||||
# that are non-excluded candidates that can be scanned for target directories and files
|
||||
# to copy over to the install layout
|
||||
foreach(filtered_asset_path IN LISTS filtered_asset_paths)
|
||||
if(IS_DIRECTORY ${filtered_asset_path})
|
||||
file(GLOB_RECURSE
|
||||
recurse_assets_paths
|
||||
LIST_DIRECTORIES TRUE
|
||||
"${filtered_asset_path}/*"
|
||||
)
|
||||
set(gem_file_paths ${recurse_assets_paths})
|
||||
# Make sure to prepend the current path iteration to the gem_dirs_path to filter
|
||||
set(gem_dir_paths ${filtered_asset_path} ${recurse_assets_paths})
|
||||
# Iterate over each gem candidate directories and read populate a directory property
|
||||
# containing the files to copy over
|
||||
get_property(gem_candidate_dirs GLOBAL PROPERTY global_gem_candidate_dirs_prop)
|
||||
foreach(gem_candidate_dir IN LISTS gem_candidate_dirs)
|
||||
get_property(filtered_asset_paths DIRECTORY ${gem_candidate_dir} PROPERTY directory_filtered_asset_paths)
|
||||
ly_get_last_path_segment_concat_sha256(${gem_candidate_dir} last_gem_root_path_segment)
|
||||
# Check if the gem is a subdirectory of the engine
|
||||
cmake_path(IS_PREFIX LY_ROOT_FOLDER ${gem_candidate_dir} is_gem_subdirectory_of_engine)
|
||||
|
||||
# At this point the filtered_assets_paths contains the list of all directories and files
|
||||
# that are non-excluded candidates that can be scanned for target directories and files
|
||||
# to copy over to the install layout
|
||||
foreach(filtered_asset_path IN LISTS filtered_asset_paths)
|
||||
if(IS_DIRECTORY ${filtered_asset_path})
|
||||
file(GLOB_RECURSE
|
||||
recurse_assets_paths
|
||||
LIST_DIRECTORIES TRUE
|
||||
"${filtered_asset_path}/*"
|
||||
)
|
||||
set(gem_file_paths ${recurse_assets_paths})
|
||||
# Make sure to prepend the current path iteration to the gem_dirs_path to filter
|
||||
set(gem_dir_paths ${filtered_asset_path} ${recurse_assets_paths})
|
||||
|
||||
# Gather directories to copy over
|
||||
# Currently only the Assets, Registry and Config directories are copied over
|
||||
list(FILTER gem_dir_paths INCLUDE REGEX "/(Assets|Registry|Config)$")
|
||||
list(APPEND gems_assets_dir_path ${gem_dir_paths})
|
||||
else()
|
||||
set(gem_file_paths ${filtered_asset_path})
|
||||
endif()
|
||||
# Gather directories to copy over
|
||||
# Currently only the Assets, Registry and Config directories are copied over
|
||||
list(FILTER gem_dir_paths INCLUDE REGEX "/(Assets|Registry|Config|Editor/Scripts)$")
|
||||
set_property(DIRECTORY ${gem_candidate_dir} APPEND PROPERTY gems_assets_paths ${gem_dir_paths})
|
||||
else()
|
||||
set(gem_file_paths ${filtered_asset_path})
|
||||
endif()
|
||||
|
||||
# Gather files to copy over
|
||||
# Currently only the gem.json file is copied over
|
||||
list(FILTER gem_file_paths INCLUDE REGEX "/(gem.json)$")
|
||||
list(APPEND gems_assets_file_path "${gem_file_paths}")
|
||||
endforeach()
|
||||
# Gather files to copy over
|
||||
# Currently only the gem.json file is copied over
|
||||
list(FILTER gem_file_paths INCLUDE REGEX "/(gem.json|preview.png)$")
|
||||
set_property(DIRECTORY ${gem_candidate_dir} APPEND PROPERTY gems_assets_paths "${gem_file_paths}")
|
||||
endforeach()
|
||||
|
||||
# gem directories to install
|
||||
foreach(gem_absolute_dir_path ${gems_assets_dir_path})
|
||||
cmake_path(RELATIVE_PATH gem_absolute_dir_path BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE gem_relative_dir_path)
|
||||
if (EXISTS ${gem_absolute_dir_path})
|
||||
# The trailing slash is IMPORTANT here as that is needed to prevent
|
||||
# the "Assets" folder from being copied underneath the <gem-root>/Assets folder
|
||||
install(DIRECTORY "${gem_absolute_dir_path}/"
|
||||
DESTINATION ${gem_relative_dir_path}
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
# gem directories and files to install
|
||||
get_property(gems_assets_paths DIRECTORY ${gem_candidate_dir} PROPERTY gems_assets_paths)
|
||||
foreach(gem_absolute_path IN LISTS gems_assets_paths)
|
||||
if(is_gem_subdirectory_of_engine)
|
||||
cmake_path(RELATIVE_PATH gem_absolute_path BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE gem_install_dest_dir)
|
||||
else()
|
||||
# The gem resides outside of the LY_ROOT_FOLDER, so the destination is made relative to the
|
||||
# gem candidate directory and placed under the "External" directory"
|
||||
# directory
|
||||
cmake_path(RELATIVE_PATH gem_absolute_path BASE_DIRECTORY ${gem_candidate_dir} OUTPUT_VARIABLE gem_relative_path)
|
||||
unset(gem_install_dest_dir)
|
||||
cmake_path(APPEND gem_install_dest_dir "External" ${last_gem_root_path_segment} ${gem_relative_path})
|
||||
endif()
|
||||
|
||||
cmake_path(GET gem_install_dest_dir PARENT_PATH gem_install_dest_dir)
|
||||
if (NOT gem_install_dest_dir)
|
||||
cmake_path(SET gem_install_dest_dir .)
|
||||
endif()
|
||||
if(IS_DIRECTORY ${gem_absolute_path})
|
||||
install(DIRECTORY "${gem_absolute_path}" DESTINATION ${gem_install_dest_dir})
|
||||
elseif (EXISTS ${gem_absolute_path})
|
||||
install(FILES ${gem_absolute_path} DESTINATION ${gem_install_dest_dir})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# gem files to install
|
||||
foreach(gem_absolute_file_path ${gems_assets_file_path})
|
||||
cmake_path(RELATIVE_PATH gem_absolute_file_path BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE gem_relative_file_path)
|
||||
cmake_path(GET gem_relative_file_path PARENT_PATH gem_relative_parent_dir)
|
||||
install(FILES ${gem_absolute_file_path}
|
||||
DESTINATION ${gem_relative_parent_dir}
|
||||
)
|
||||
endforeach()
|
||||
|
||||
# Templates
|
||||
@@ -626,3 +655,46 @@ function(ly_setup_target_generator)
|
||||
)
|
||||
|
||||
endfunction()
|
||||
|
||||
#! ly_add_install_paths: Adds the list of path to copy to the install layout relative to the same folder
|
||||
# \arg:PATHS - Paths to copy over to the install layout. The DESTINATION sub argument is optional
|
||||
# The INPUT sub-argument is required
|
||||
# \arg:BASE_DIRECTORY(Optional) - Absolute path where a relative path from the each input path will be
|
||||
# based off of. Defaults to LY_ROOT_FOLDER if not supplied
|
||||
function(ly_add_install_paths)
|
||||
set(options)
|
||||
set(oneValueArgs BASE_DIRECTORY)
|
||||
set(multiValueArgs PATHS)
|
||||
cmake_parse_arguments(ly_add_install_paths "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
if(NOT ly_add_install_paths_PATHS)
|
||||
message(FATAL_ERROR "ly_add_install_paths requires at least one input path to copy to the destination")
|
||||
endif()
|
||||
|
||||
# The default is the "." directory if not supplied
|
||||
if(NOT ly_add_install_paths_BASE_DIRECTORY)
|
||||
cmake_path(SET ly_add_install_paths_BASE_DIRECTORY ${LY_ROOT_FOLDER})
|
||||
endif()
|
||||
|
||||
# Separate each path into an INPUT and DESTINATION parameter
|
||||
set(options)
|
||||
set(oneValueArgs INPUT DESTINATION)
|
||||
set(multiValueArgs)
|
||||
foreach(install_path IN LISTS ly_add_install_paths_PATHS)
|
||||
string(REPLACE " " ";" install_path ${install_path})
|
||||
cmake_parse_arguments(install "${options}" "${oneValueArgs}" "${multiValueArgs}" ${install_path})
|
||||
if(NOT install_DESTINATION)
|
||||
ly_get_engine_relative_source_dir(${install_INPUT} rel_to_root_input_path
|
||||
BASE_DIRECTORY ${ly_add_install_paths_BASE_DIRECTORY})
|
||||
cmake_path(GET rel_to_root_input_path PARENT_PATH install_DESTINATION)
|
||||
endif()
|
||||
if(NOT install_DESTINATION)
|
||||
cmake_path(SET install_DESTINATION .)
|
||||
endif()
|
||||
if(IS_DIRECTORY ${install_INPUT})
|
||||
install(DIRECTORY ${install_INPUT} DESTINATION ${install_DESTINATION})
|
||||
elseif(EXISTS ${install_INPUT})
|
||||
install(FILES ${install_INPUT} DESTINATION ${install_DESTINATION})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
endfunction()
|
||||
|
||||
@@ -8,39 +8,16 @@
|
||||
include(cmake/Platform/Common/Configurations_common.cmake)
|
||||
include(cmake/Platform/Common/VisualStudio_common.cmake)
|
||||
|
||||
set(LY_MSVC_SUPPORTED_GENERATORS
|
||||
"Visual Studio 15"
|
||||
"Visual Studio 16"
|
||||
)
|
||||
set(FOUND_SUPPORTED_GENERATOR)
|
||||
foreach(supported_generator ${LY_MSVC_SUPPORTED_GENERATORS})
|
||||
if(CMAKE_GENERATOR MATCHES ${supported_generator})
|
||||
set(FOUND_SUPPORTED_GENERATOR TRUE)
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
# VS2017's checks since it defaults the toolchain and target architecture to x86
|
||||
if(CMAKE_GENERATOR MATCHES "Visual Studio 15")
|
||||
if(CMAKE_VS_PLATFORM_NAME AND CMAKE_VS_PLATFORM_NAME STREQUAL "Win32") # VS2017 has Win32 as the default architecture
|
||||
message(FATAL_ERROR "Win32 architecture not supported, specify \"-A x64\" when invoking cmake")
|
||||
endif()
|
||||
if(NOT CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE STREQUAL "x64") # There is at least one library (EditorLib) that make the x86 linker to run out of memory
|
||||
message(FATAL_ERROR "x86 toolset not supported, specify \"-T host=x64\" when invoking cmake")
|
||||
endif()
|
||||
else()
|
||||
# For the other cases, verify that it wasn't invoked with an unsupported architecture. defaults to x86 architecture
|
||||
if(SUPPORTED_VS_PLATFORM_NAME_OVERRIDE)
|
||||
set(SUPPORTED_VS_PLATFORM_NAME ${SUPPORTED_VS_PLATFORM_NAME_OVERRIDE})
|
||||
else()
|
||||
set(SUPPORTED_VS_PLATFORM_NAME x64)
|
||||
endif()
|
||||
if(NOT CMAKE_GENERATOR MATCHES "Visual Studio 1[6-7]")
|
||||
message(FATAL_ERROR "Generator ${CMAKE_GENERATOR} not supported")
|
||||
endif()
|
||||
|
||||
if(CMAKE_VS_PLATFORM_NAME AND NOT CMAKE_VS_PLATFORM_NAME STREQUAL "${SUPPORTED_VS_PLATFORM_NAME}")
|
||||
message(FATAL_ERROR "${CMAKE_VS_PLATFORM_NAME} architecture not supported")
|
||||
endif()
|
||||
if(CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE AND NOT CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE STREQUAL "x64")
|
||||
message(FATAL_ERROR "${CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE} toolset not supported")
|
||||
endif()
|
||||
# Verify that it wasn't invoked with an unsupported target/host architecture. Currently only supports x64/x64
|
||||
if(CMAKE_VS_PLATFORM_NAME AND NOT CMAKE_VS_PLATFORM_NAME STREQUAL "x64")
|
||||
message(FATAL_ERROR "${CMAKE_VS_PLATFORM_NAME} target architecture is not supported, it must be 'x64'")
|
||||
endif()
|
||||
if(CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE AND NOT CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE STREQUAL "x64")
|
||||
message(FATAL_ERROR "${CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE} host toolset is not supported, it must be 'x64'")
|
||||
endif()
|
||||
|
||||
ly_append_configurations_options(
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
#
|
||||
#
|
||||
|
||||
if(CMAKE_GENERATOR MATCHES "Visual Studio 16")
|
||||
configure_file("${CMAKE_CURRENT_LIST_DIR}/Directory.Build.props" "${CMAKE_BINARY_DIR}/Directory.Build.props" COPYONLY)
|
||||
endif()
|
||||
foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES)
|
||||
if(conf STREQUAL debug)
|
||||
string(APPEND VCPKG_CONFIGURATION_MAPPING " <VcpkgConfiguration Condition=\"'$(Configuration)' == '${conf}'\">Debug</VcpkgConfiguration>\n")
|
||||
else()
|
||||
string(APPEND VCPKG_CONFIGURATION_MAPPING " <VcpkgConfiguration Condition=\"'$(Configuration)' == '${conf}'\">Release</VcpkgConfiguration>\n")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
configure_file("${CMAKE_CURRENT_LIST_DIR}/Directory.Build.props" "${CMAKE_BINARY_DIR}/Directory.Build.props" @ONLY)
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ function(ly_copy source_file target_directory)
|
||||
if("${source_file}" IS_NEWER_THAN "${target_directory}/${target_filename}")
|
||||
message(STATUS "Copying \"${source_file}\" to \"${target_directory}\"...")
|
||||
file(COPY "${source_file}" DESTINATION "${target_directory}" FILE_PERMISSIONS @LY_COPY_PERMISSIONS@ FOLLOW_SYMLINK_CHAIN)
|
||||
file(TOUCH_NOCREATE ${target_directory}/${target_filename})
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
@@ -125,7 +125,7 @@ function(ly_copy source_file target_directory)
|
||||
file(LOCK ${target_directory}/${target_filename}.lock GUARD FUNCTION TIMEOUT 300)
|
||||
endif()
|
||||
file(COPY "${source_file}" DESTINATION "${target_directory}" FILE_PERMISSIONS @LY_COPY_PERMISSIONS@ FOLLOW_SYMLINK_CHAIN)
|
||||
file(TOUCH ${target_directory}/${target_filename})
|
||||
file(TOUCH_NOCREATE ${target_directory}/${target_filename})
|
||||
set(anything_new TRUE PARENT_SCOPE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
<Window Width="672" Height="500" HexStyle="100a0000" FontId="2">#(loc.WindowTitle)</Window>
|
||||
|
||||
<!-- header font -->
|
||||
<Font Id="0" Height="-24" Weight="500" Foreground="000000">Open Sans</Font>
|
||||
<Font Id="0" Height="-24" Weight="500" Foreground="000000">Segoe UI</Font>
|
||||
<!-- body font -->
|
||||
<Font Id="1" Height="-12" Weight="500" Foreground="000000">Open Sans</Font>
|
||||
<Font Id="1" Height="-12" Weight="500" Foreground="000000">Segoe UI</Font>
|
||||
<!-- special font -->
|
||||
<Font Id="2" Height="-12" Weight="500" Foreground="000000" Background="FFFFFF">Open Sans</Font>
|
||||
<Font Id="2" Height="-12" Weight="500" Foreground="000000" Background="FFFFFF">Segoe UI</Font>
|
||||
<!-- throwaway font for button bar -->
|
||||
<Font Id="3" Height="-12" Weight="500" Foreground="EEEEEE" Background="EEEEEE">Open Sans</Font>
|
||||
<Font Id="3" Height="-12" Weight="500" Foreground="EEEEEE" Background="EEEEEE">Segoe UI</Font>
|
||||
<!-- special font for install warning title -->
|
||||
<Font Id="4" Height="-14" Weight="500" Foreground="000000">Open Sans</Font>
|
||||
<Font Id="4" Height="-14" Weight="500" Foreground="000000">Segoe UI</Font>
|
||||
|
||||
<!-- top banner -->
|
||||
<Image X="28" Y="24" Width="168" Height="64" ImageFile="logo.png" Visible="yes"/>
|
||||
|
||||
@@ -158,10 +158,6 @@ function(ly_delayed_generate_settings_registry)
|
||||
message(FATAL_ERROR "Dependency ${gem_target} from ${target} does not exist")
|
||||
endif()
|
||||
|
||||
get_property(has_manually_added_dependencies TARGET ${gem_target} PROPERTY MANUALLY_ADDED_DEPENDENCIES SET)
|
||||
get_target_property(target_type ${gem_target} TYPE)
|
||||
|
||||
|
||||
ly_get_gem_module_root(gem_module_root ${gem_target})
|
||||
file(RELATIVE_PATH gem_module_root_relative_to_engine_root ${LY_ROOT_FOLDER} ${gem_module_root})
|
||||
|
||||
@@ -179,7 +175,8 @@ function(ly_delayed_generate_settings_registry)
|
||||
list(JOIN target_gem_dependencies_names ",\n" target_gem_dependencies_names)
|
||||
string(CONFIGURE ${gems_json_template} gem_json @ONLY)
|
||||
get_target_property(is_imported ${target} IMPORTED)
|
||||
if(is_imported)
|
||||
get_target_property(target_type ${target} TYPE)
|
||||
if(is_imported OR target_type STREQUAL UTILITY)
|
||||
unset(target_dir)
|
||||
foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES)
|
||||
string(TOUPPER ${conf} UCONF)
|
||||
|
||||
@@ -17,6 +17,8 @@ ly_add_target(
|
||||
@RUNTIME_DEPENDENCIES_PLACEHOLDER@
|
||||
)
|
||||
|
||||
@TARGET_RUN_HELPER@
|
||||
|
||||
set(configs @CMAKE_CONFIGURATION_TYPES@)
|
||||
foreach(config ${configs})
|
||||
include("@NAME_PLACEHOLDER@_${config}.cmake" OPTIONAL)
|
||||
|
||||
Vendored
+12
-15
@@ -204,8 +204,6 @@ def CheckoutRepo(boolean disableSubmodules = false) {
|
||||
if (!fileExists(ENGINE_REPOSITORY_NAME)) {
|
||||
palMkdir(ENGINE_REPOSITORY_NAME)
|
||||
}
|
||||
|
||||
palSh('git lfs uninstall', 'Git LFS Uninstall') // Prevent git from pulling lfs objects during checkout
|
||||
|
||||
if(fileExists('.git')) {
|
||||
// If the repository after checkout is locked, likely we took a snapshot while git was running,
|
||||
@@ -239,13 +237,6 @@ def CheckoutRepo(boolean disableSubmodules = false) {
|
||||
]
|
||||
}
|
||||
|
||||
// Run lfs in a separate step. Jenkins is unable to load the credentials for the custom LFS endpoint
|
||||
withCredentials([usernamePassword(credentialsId: "${env.GITHUB_USER}", passwordVariable: 'accesstoken', usernameVariable: 'username')]) {
|
||||
palSh("git config -f .lfsconfig lfs.url https://${username}:${accesstoken}@${env.LFS_URL}", 'Set credentials', false)
|
||||
}
|
||||
palSh('git lfs install', 'Git LFS Install')
|
||||
palSh('git lfs pull', 'Git LFS Pull')
|
||||
|
||||
// CHANGE_ID is used by some scripts to identify uniquely the current change (usually metric jobs)
|
||||
palSh('git rev-parse HEAD > commitid', 'Getting commit id')
|
||||
env.CHANGE_ID = readFile file: 'commitid'
|
||||
@@ -586,13 +577,19 @@ finally {
|
||||
)
|
||||
}
|
||||
node('controller') {
|
||||
step([
|
||||
$class: 'Mailer',
|
||||
notifyEveryUnstableBuild: true,
|
||||
recipients: emailextrecipients([
|
||||
if("${currentBuild.currentResult}" == "SUCCESS") {
|
||||
emailBody = "${BUILD_URL}\nSuccess!"
|
||||
} else {
|
||||
buildFailure = tm('${BUILD_FAILURE_ANALYZER}')
|
||||
emailBody = "${BUILD_URL}\n${buildFailure}!"
|
||||
}
|
||||
emailext (
|
||||
body: "${emailBody}",
|
||||
subject: "${currentBuild.currentResult}: ${JOB_NAME} - Build # ${BUILD_NUMBER}",
|
||||
recipientProviders: [
|
||||
[$class: 'RequesterRecipientProvider']
|
||||
])
|
||||
])
|
||||
]
|
||||
)
|
||||
}
|
||||
} catch(Exception e) {
|
||||
}
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
#
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import boto3
|
||||
import datetime
|
||||
import urllib.request, urllib.error, urllib.parse
|
||||
import os
|
||||
import psutil
|
||||
import time
|
||||
import requests
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
from contextlib import contextmanager
|
||||
import threading
|
||||
import _thread
|
||||
|
||||
DEFAULT_REGION = 'us-west-2'
|
||||
DEFAULT_DISK_SIZE = 300
|
||||
@@ -42,14 +42,18 @@ if os.name == 'nt':
|
||||
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
|
||||
kernel32.GetDiskFreeSpaceExW.argtypes = (ctypes.c_wchar_p,) + (PULARGE_INTEGER,) * 3
|
||||
|
||||
|
||||
class UsageTuple(collections.namedtuple('UsageTuple', 'total, used, free')):
|
||||
def __str__(self):
|
||||
# Add thousands separator to numbers displayed
|
||||
return self.__class__.__name__ + '(total={:n}, used={:n}, free={:n})'.format(*self)
|
||||
|
||||
|
||||
def is_dir_symlink(path):
|
||||
FILE_ATTRIBUTE_REPARSE_POINT = 0x0400
|
||||
return os.path.isdir(path) and (ctypes.windll.kernel32.GetFileAttributesW(str(path)) & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
return os.path.isdir(path) and (
|
||||
ctypes.windll.kernel32.GetFileAttributesW(str(path)) & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
|
||||
|
||||
def get_free_space_mb(path):
|
||||
if sys.version_info < (3,): # Python 2?
|
||||
@@ -77,16 +81,39 @@ if os.name == 'nt':
|
||||
|
||||
used = total.value - free.value
|
||||
|
||||
return free.value / 1024 / 1024#for now
|
||||
return free.value / 1024 / 1024 # for now
|
||||
else:
|
||||
def get_free_space_mb(dirname):
|
||||
st = os.statvfs(dirname)
|
||||
return st.f_bavail * st.f_frsize / 1024 / 1024
|
||||
|
||||
|
||||
def error(message):
|
||||
print(message)
|
||||
exit(1)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def timeout(duration, timeout_message):
|
||||
timer = threading.Timer(duration, lambda: _thread.interrupt_main())
|
||||
timer.start()
|
||||
try:
|
||||
yield
|
||||
except KeyboardInterrupt:
|
||||
print(timeout_message)
|
||||
raise TimeoutError
|
||||
finally:
|
||||
# If the action ends in specified time, timer is canceled
|
||||
timer.cancel()
|
||||
|
||||
|
||||
def print_drives():
|
||||
if os.name == 'nt':
|
||||
drives_before = win32api.GetLogicalDriveStrings()
|
||||
drives_before = drives_before.split('\000')[:-1]
|
||||
print(drives_before)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-a', '--action', dest="action", help="Action (mount|unmount|delete)")
|
||||
@@ -96,8 +123,10 @@ def parse_args():
|
||||
parser.add_argument('-b', '--branch', dest="branch", help="Branch")
|
||||
parser.add_argument('-plat', '--platform', dest="platform", help="Platform")
|
||||
parser.add_argument('-c', '--build_type', dest="build_type", help="Build type")
|
||||
parser.add_argument('-ds', '--disk_size', dest="disk_size", help="Disk size in Gigabytes (defaults to {})".format(DEFAULT_DISK_SIZE), default=DEFAULT_DISK_SIZE)
|
||||
parser.add_argument('-dt', '--disk_type', dest="disk_type", help="Disk type (defaults to {})".format(DEFAULT_DISK_TYPE), default=DEFAULT_DISK_TYPE)
|
||||
parser.add_argument('-ds', '--disk_size', dest="disk_size",
|
||||
help=f"Disk size in Gigabytes (defaults to {DEFAULT_DISK_SIZE})", default=DEFAULT_DISK_SIZE)
|
||||
parser.add_argument('-dt', '--disk_type', dest="disk_type", help=f"Disk type (defaults to {DEFAULT_DISK_TYPE})",
|
||||
default=DEFAULT_DISK_TYPE)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Input validation
|
||||
@@ -117,19 +146,30 @@ def parse_args():
|
||||
error('No platform specified')
|
||||
if args.build_type is None:
|
||||
error('No build_type specified')
|
||||
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def get_mount_name(repository_name, project, pipeline, branch, platform, build_type):
|
||||
mount_name = "{}_{}_{}_{}_{}_{}".format(repository_name, project, pipeline, branch, platform, build_type)
|
||||
mount_name = mount_name.replace('/','_').replace('\\','_')
|
||||
mount_name = f"{repository_name}_{project}_{pipeline}_{branch}_{platform}_{build_type}"
|
||||
mount_name = mount_name.replace('/', '_').replace('\\', '_')
|
||||
return mount_name
|
||||
|
||||
|
||||
def get_pipeline_and_branch(pipeline, branch):
|
||||
pipeline_and_branch = "{}_{}".format(pipeline, branch)
|
||||
pipeline_and_branch = pipeline_and_branch.replace('/','_').replace('\\','_')
|
||||
pipeline_and_branch = f"{pipeline}_{branch}"
|
||||
pipeline_and_branch = pipeline_and_branch.replace('/', '_').replace('\\', '_')
|
||||
return pipeline_and_branch
|
||||
|
||||
|
||||
def get_region_name():
|
||||
session = boto3.session.Session()
|
||||
region = session.region_name
|
||||
if region is None:
|
||||
region = DEFAULT_REGION
|
||||
return region
|
||||
|
||||
|
||||
def get_ec2_client(region):
|
||||
client = boto3.client('ec2', region_name=region)
|
||||
return client
|
||||
@@ -140,48 +180,51 @@ def get_ec2_instance_id():
|
||||
instance_id = urllib.request.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read()
|
||||
return instance_id.decode("utf-8")
|
||||
except Exception as e:
|
||||
print(e.message)
|
||||
print(e)
|
||||
error('No EC2 metadata! Check if you are running this script on an EC2 instance.')
|
||||
|
||||
|
||||
def get_availability_zone():
|
||||
try:
|
||||
availability_zone = urllib.request.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()
|
||||
availability_zone = urllib.request.urlopen(
|
||||
'http://169.254.169.254/latest/meta-data/placement/availability-zone').read()
|
||||
return availability_zone.decode("utf-8")
|
||||
except Exception as e:
|
||||
print(e.message)
|
||||
print(e)
|
||||
error('No EC2 metadata! Check if you are running this script on an EC2 instance.')
|
||||
|
||||
|
||||
def kill_processes(workspace='/dev/'):
|
||||
'''
|
||||
"""
|
||||
Kills all processes that have open file paths associated with the workspace.
|
||||
Uses PSUtil for cross-platform compatibility
|
||||
'''
|
||||
"""
|
||||
print('Checking for any stuck processes...')
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
if workspace in str(proc.open_files()):
|
||||
print("{} has open files in {}. Terminating".format(proc.name(), proc.open_files()))
|
||||
print(f"{proc.name()} has open files in {proc.open_files()}. Terminating")
|
||||
proc.kill()
|
||||
time.sleep(1) # Just to make sure a parent process has time to close
|
||||
time.sleep(1) # Just to make sure a parent process has time to close
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
continue
|
||||
|
||||
|
||||
def delete_volume(ec2_client, volume_id):
|
||||
response = ec2_client.delete_volume(VolumeId=volume_id)
|
||||
print('Volume {} deleted'.format(volume_id))
|
||||
print(f'Volume {volume_id} deleted')
|
||||
|
||||
|
||||
def find_snapshot_id(ec2_client, repository_name, project, pipeline, platform, build_type, disk_size):
|
||||
mount_name = get_mount_name(repository_name, project, pipeline, 'stabilization_2106', platform, build_type) # we take snapshots out of stabilization_2106
|
||||
response = ec2_client.describe_snapshots(Filters= [{
|
||||
mount_name = get_mount_name(repository_name, project, pipeline, 'stabilization_2106', platform,
|
||||
build_type) # we take snapshots out of stabilization_2106
|
||||
response = ec2_client.describe_snapshots(Filters=[{
|
||||
'Name': 'tag:Name', 'Values': [mount_name]
|
||||
}])
|
||||
|
||||
snapshot_id = None
|
||||
if 'Snapshots' in response and len(response['Snapshots']) > 0:
|
||||
snapshot_start_time_max = None # find the latest snapshot
|
||||
snapshot_start_time_max = None # find the latest snapshot
|
||||
for snapshot in response['Snapshots']:
|
||||
if snapshot['State'] == 'completed' and snapshot['VolumeSize'] == disk_size:
|
||||
snapshot_start_time = snapshot['StartTime']
|
||||
@@ -190,28 +233,33 @@ def find_snapshot_id(ec2_client, repository_name, project, pipeline, platform, b
|
||||
snapshot_id = snapshot['SnapshotId']
|
||||
return snapshot_id
|
||||
|
||||
def create_volume(ec2_client, availability_zone, repository_name, project, pipeline, branch, platform, build_type, disk_size, disk_type):
|
||||
# The actual EBS default calculation for IOps is a floating point number, the closest approxmiation is 4x of the disk size for simplicity
|
||||
|
||||
def create_volume(ec2_client, availability_zone, repository_name, project, pipeline, branch, platform, build_type,
|
||||
disk_size, disk_type):
|
||||
mount_name = get_mount_name(repository_name, project, pipeline, branch, platform, build_type)
|
||||
pipeline_and_branch = get_pipeline_and_branch(pipeline, branch)
|
||||
pipeline_and_branch = get_pipeline_and_branch(pipeline, branch)
|
||||
parameters = dict(
|
||||
AvailabilityZone = availability_zone,
|
||||
AvailabilityZone=availability_zone,
|
||||
VolumeType=disk_type,
|
||||
TagSpecifications= [{
|
||||
TagSpecifications=[{
|
||||
'ResourceType': 'volume',
|
||||
'Tags': [
|
||||
{ 'Key': 'Name', 'Value': mount_name },
|
||||
{ 'Key': 'RepositoryName', 'Value': repository_name},
|
||||
{ 'Key': 'Project', 'Value': project },
|
||||
{ 'Key': 'Pipeline', 'Value': pipeline },
|
||||
{ 'Key': 'BranchName', 'Value': branch },
|
||||
{ 'Key': 'Platform', 'Value': platform },
|
||||
{ 'Key': 'BuildType', 'Value': build_type },
|
||||
{ 'Key': 'PipelineAndBranch', 'Value': pipeline_and_branch }, # used so the snapshoting easily identifies which volumes to snapshot
|
||||
{'Key': 'Name', 'Value': mount_name},
|
||||
{'Key': 'RepositoryName', 'Value': repository_name},
|
||||
{'Key': 'Project', 'Value': project},
|
||||
{'Key': 'Pipeline', 'Value': pipeline},
|
||||
{'Key': 'BranchName', 'Value': branch},
|
||||
{'Key': 'Platform', 'Value': platform},
|
||||
{'Key': 'BuildType', 'Value': build_type},
|
||||
# Used so the snapshoting easily identifies which volumes to snapshot
|
||||
{'Key': 'PipelineAndBranch', 'Value': pipeline_and_branch},
|
||||
|
||||
]
|
||||
}]
|
||||
)
|
||||
if 'io1' in disk_type.lower():
|
||||
# The actual EBS default calculation for IOps is a floating point number,
|
||||
# the closest approxmiation is 4x of the disk size for simplicity
|
||||
if 'io1' in disk_type.lower():
|
||||
parameters['Iops'] = (4 * disk_size)
|
||||
|
||||
snapshot_id = find_snapshot_id(ec2_client, repository_name, project, pipeline, platform, build_type, disk_size)
|
||||
@@ -230,16 +278,17 @@ def create_volume(ec2_client, availability_zone, repository_name, project, pipel
|
||||
time.sleep(1)
|
||||
|
||||
response = ec2_client.describe_volumes(VolumeIds=[volume_id, ])
|
||||
while (response['Volumes'][0]['State'] != 'available'):
|
||||
time.sleep(1)
|
||||
response = ec2_client.describe_volumes(VolumeIds=[volume_id, ])
|
||||
with timeout(DEFAULT_TIMEOUT, 'ERROR: Timeout reached trying to create EBS.'):
|
||||
while response['Volumes'][0]['State'] != 'available':
|
||||
time.sleep(1)
|
||||
response = ec2_client.describe_volumes(VolumeIds=[volume_id, ])
|
||||
|
||||
print(("Volume {} created\n\tSnapshot: {}\n\tRepository {}\n\tProject {}\n\tPipeline {}\n\tBranch {}\n\tPlatform: {}\n\tBuild type: {}"
|
||||
.format(volume_id, snapshot_id, repository_name, project, pipeline, branch, platform, build_type)))
|
||||
print(f"Volume {volume_id} created\n\tSnapshot: {snapshot_id}\n\tRepository {repository_name}\n\t"
|
||||
f"Project {project}\n\tPipeline {pipeline}\n\tBranch {branch}\n\tPlatform: {platform}\n\tBuild type: {build_type}")
|
||||
return volume_id, created
|
||||
|
||||
|
||||
def mount_volume(created):
|
||||
def mount_volume_to_device(created):
|
||||
print('Mounting volume...')
|
||||
if os.name == 'nt':
|
||||
f = tempfile.NamedTemporaryFile(delete=False)
|
||||
@@ -247,7 +296,7 @@ def mount_volume(created):
|
||||
select disk 1
|
||||
online disk
|
||||
attribute disk clear readonly
|
||||
""".encode('utf-8')) # assume disk # for now
|
||||
""".encode('utf-8')) # assume disk # for now
|
||||
|
||||
if created:
|
||||
print('Creating filesystem on new volume')
|
||||
@@ -259,18 +308,12 @@ def mount_volume(created):
|
||||
""".encode('utf-8'))
|
||||
|
||||
f.close()
|
||||
|
||||
|
||||
subprocess.call(['diskpart', '/s', f.name])
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
drives_after = win32api.GetLogicalDriveStrings()
|
||||
drives_after = drives_after.split('\000')[:-1]
|
||||
|
||||
print(drives_after)
|
||||
|
||||
#drive_letter = next(item for item in drives_after if item not in drives_before)
|
||||
drive_letter = MOUNT_PATH
|
||||
print_drives()
|
||||
|
||||
os.unlink(f.name)
|
||||
|
||||
@@ -283,8 +326,8 @@ def mount_volume(created):
|
||||
subprocess.call(['mount', '/dev/xvdf', MOUNT_PATH])
|
||||
|
||||
|
||||
def attach_volume(volume, volume_id, instance_id, timeout=DEFAULT_TIMEOUT):
|
||||
print('Attaching volume {} to instance {}'.format(volume_id, instance_id))
|
||||
def attach_volume_to_ec2_instance(volume, volume_id, instance_id, timeout_duration=DEFAULT_TIMEOUT):
|
||||
print(f'Attaching volume {volume_id} to instance {instance_id}')
|
||||
volume.attach_to_instance(Device='xvdf',
|
||||
InstanceId=instance_id,
|
||||
VolumeId=volume_id)
|
||||
@@ -292,13 +335,10 @@ def attach_volume(volume, volume_id, instance_id, timeout=DEFAULT_TIMEOUT):
|
||||
time.sleep(2)
|
||||
# reload the volume just in case
|
||||
volume.load()
|
||||
timeout_init = time.clock()
|
||||
while (len(volume.attachments) and volume.attachments[0]['State'] != 'attached'):
|
||||
time.sleep(1)
|
||||
volume.load()
|
||||
if (time.clock() - timeout_init) > timeout:
|
||||
print('ERROR: Timeout reached trying to mount EBS')
|
||||
exit(1)
|
||||
with timeout(timeout_duration, 'ERROR: Timeout reached trying to mount EBS.'):
|
||||
while len(volume.attachments) and volume.attachments[0]['State'] != 'attached':
|
||||
time.sleep(1)
|
||||
volume.load()
|
||||
volume.create_tags(
|
||||
Tags=[
|
||||
{
|
||||
@@ -307,11 +347,11 @@ def attach_volume(volume, volume_id, instance_id, timeout=DEFAULT_TIMEOUT):
|
||||
},
|
||||
]
|
||||
)
|
||||
print('Volume {} has been attached to instance {}'.format(volume_id, instance_id))
|
||||
print(f'Volume {volume_id} has been attached to instance {instance_id}')
|
||||
|
||||
|
||||
def unmount_volume():
|
||||
print('Umounting volume...')
|
||||
def unmount_volume_from_device():
|
||||
print('Unmounting EBS volume from device...')
|
||||
if os.name == 'nt':
|
||||
kill_processes(MOUNT_PATH + 'workspace')
|
||||
f = tempfile.NamedTemporaryFile(delete=False)
|
||||
@@ -327,50 +367,31 @@ def unmount_volume():
|
||||
subprocess.call(['umount', '-f', MOUNT_PATH])
|
||||
|
||||
|
||||
def detach_volume(volume, ec2_instance_id, force, timeout=DEFAULT_TIMEOUT):
|
||||
print('Detaching volume {} from instance {}'.format(volume.volume_id, ec2_instance_id))
|
||||
def detach_volume_from_ec2_instance(volume, ec2_instance_id, force, timeout_duration=DEFAULT_TIMEOUT):
|
||||
print(f'Detaching volume {volume.volume_id} from instance {ec2_instance_id}')
|
||||
volume.detach_from_instance(Device='xvdf',
|
||||
Force=force,
|
||||
InstanceId=ec2_instance_id,
|
||||
VolumeId=volume.volume_id)
|
||||
timeout_init = time.clock()
|
||||
while len(volume.attachments) and volume.attachments[0]['State'] != 'detached':
|
||||
time.sleep(1)
|
||||
volume.load()
|
||||
if (time.clock() - timeout_init) > timeout:
|
||||
print('ERROR: Timeout reached trying to unmount EBS.')
|
||||
volume.detach_from_instance(Device='xvdf',Force=True,InstanceId=ec2_instance_id,VolumeId=volume.volume_id)
|
||||
exit(1)
|
||||
|
||||
print('Volume {} has been detached from instance {}'.format(volume.volume_id, ec2_instance_id))
|
||||
try:
|
||||
with timeout(timeout_duration, 'ERROR: Timeout reached trying to unmount EBS.'):
|
||||
while len(volume.attachments) and volume.attachments[0]['State'] != 'detached':
|
||||
time.sleep(1)
|
||||
volume.load()
|
||||
except TimeoutError:
|
||||
print('Force detaching EBS.')
|
||||
volume.detach_from_instance(Device='xvdf', Force=True, InstanceId=ec2_instance_id, VolumeId=volume.volume_id)
|
||||
|
||||
print(f'Volume {volume.volume_id} has been detached from instance {ec2_instance_id}')
|
||||
volume.load()
|
||||
if len(volume.attachments):
|
||||
print('Volume still has attachments')
|
||||
for attachment in volume.attachments:
|
||||
print('Volume {} {} to instance {}'.format(attachment['VolumeId'], attachment['State'], attachment['InstanceId']))
|
||||
print(f"Volume {attachment['VolumeId']} {attachment['State']} to instance {attachment['InstanceId']}")
|
||||
|
||||
|
||||
def attach_ebs_and_create_partition_with_retry(volume, volume_id, ec2_instance_id, created):
|
||||
attach_volume(volume, volume_id, ec2_instance_id)
|
||||
mount_volume(created)
|
||||
attempt = 1
|
||||
while attempt <= MAX_EBS_MOUNTING_ATTEMPT:
|
||||
if os.name == 'nt':
|
||||
drives_after = win32api.GetLogicalDriveStrings()
|
||||
drives_after = drives_after.split('\000')[:-1]
|
||||
if MOUNT_PATH not in drives_after:
|
||||
print('Disk partitioning failed, retrying...')
|
||||
unmount_volume()
|
||||
detach_volume(volume, ec2_instance_id, False)
|
||||
attach_volume(volume, volume_id, ec2_instance_id)
|
||||
mount_volume(created)
|
||||
attempt += 1
|
||||
|
||||
def mount_ebs(repository_name, project, pipeline, branch, platform, build_type, disk_size, disk_type):
|
||||
session = boto3.session.Session()
|
||||
region = session.region_name
|
||||
if region is None:
|
||||
region = DEFAULT_REGION
|
||||
region = get_region_name()
|
||||
ec2_client = get_ec2_client(region)
|
||||
ec2_instance_id = get_ec2_instance_id()
|
||||
ec2_availability_zone = get_availability_zone()
|
||||
@@ -379,70 +400,70 @@ def mount_ebs(repository_name, project, pipeline, branch, platform, build_type,
|
||||
|
||||
for volume in ec2_instance.volumes.all():
|
||||
for attachment in volume.attachments:
|
||||
print('attachment device: {}'.format(attachment['Device']))
|
||||
print(f"attachment device: {attachment['Device']}")
|
||||
if 'xvdf' in attachment['Device'] and attachment['State'] != 'detached':
|
||||
print('A device is already attached to xvdf. This likely means a previous build failed to detach its ' \
|
||||
print('A device is already attached to xvdf. This likely means a previous build failed to detach its '
|
||||
'build volume. This volume is considered orphaned and will be detached from this instance.')
|
||||
unmount_volume()
|
||||
detach_volume(volume, ec2_instance_id, False) # Force unmounts should not be used, as that will cause the EBS block device driver to fail the remount
|
||||
unmount_volume_from_device()
|
||||
detach_volume_from_ec2_instance(volume, ec2_instance_id,
|
||||
False) # Force unmounts should not be used, as that will cause the EBS block device driver to fail the remount
|
||||
|
||||
mount_name = get_mount_name(repository_name, project, pipeline, branch, platform, build_type)
|
||||
response = ec2_client.describe_volumes(Filters=[{
|
||||
'Name': 'tag:Name', 'Values': [mount_name]
|
||||
}])
|
||||
}])
|
||||
|
||||
created = False
|
||||
if 'Volumes' in response and not len(response['Volumes']):
|
||||
print('Volume for {} doesn\'t exist creating it...'.format(mount_name))
|
||||
print(f'Volume for {mount_name} doesn\'t exist creating it...')
|
||||
# volume doesn't exist, create it
|
||||
volume_id, created = create_volume(ec2_client, ec2_availability_zone, repository_name, project, pipeline, branch, platform, build_type, disk_size, disk_type)
|
||||
volume_id, created = create_volume(ec2_client, ec2_availability_zone, repository_name, project, pipeline,
|
||||
branch, platform, build_type, disk_size, disk_type)
|
||||
else:
|
||||
volume = response['Volumes'][0]
|
||||
volume_id = volume['VolumeId']
|
||||
print('Current volume {} is a {} GB {}'.format(volume_id, volume['Size'], volume['VolumeType']))
|
||||
if (volume['Size'] != disk_size or volume['VolumeType'] != disk_type):
|
||||
print('Override disk attributes does not match the existing volume, deleting {} and replacing the volume'.format(volume_id))
|
||||
print(f"Current volume {volume_id} is a {volume['Size']} GB {volume['VolumeType']}")
|
||||
if volume['Size'] != disk_size or volume['VolumeType'] != disk_type:
|
||||
print(
|
||||
f'Override disk attributes does not match the existing volume, deleting {volume_id} and replacing the volume')
|
||||
delete_volume(ec2_client, volume_id)
|
||||
volume_id, created = create_volume(ec2_client, ec2_availability_zone, repository_name, project, pipeline, branch, platform, build_type, disk_size, disk_type)
|
||||
volume_id, created = create_volume(ec2_client, ec2_availability_zone, repository_name, project, pipeline,
|
||||
branch, platform, build_type, disk_size, disk_type)
|
||||
if len(volume['Attachments']):
|
||||
# this is bad we shouldn't be attached, we should have detached at the end of a build
|
||||
attachment = volume['Attachments'][0]
|
||||
print(('Volume already has attachment {}, detaching...'.format(attachment)))
|
||||
detach_volume(ec2_resource.Volume(volume_id), attachment['InstanceId'], True)
|
||||
print(f'Volume already has attachment {attachment}, detaching...')
|
||||
detach_volume_from_ec2_instance(ec2_resource.Volume(volume_id), attachment['InstanceId'], True)
|
||||
|
||||
volume = ec2_resource.Volume(volume_id)
|
||||
|
||||
if os.name == 'nt':
|
||||
drives_before = win32api.GetLogicalDriveStrings()
|
||||
drives_before = drives_before.split('\000')[:-1]
|
||||
|
||||
print(drives_before)
|
||||
|
||||
attach_ebs_and_create_partition_with_retry(volume, volume_id, ec2_instance_id, created)
|
||||
print_drives()
|
||||
attach_volume_to_ec2_instance(volume, volume_id, ec2_instance_id)
|
||||
mount_volume_to_device(created)
|
||||
print_drives()
|
||||
|
||||
free_space_mb = get_free_space_mb(MOUNT_PATH)
|
||||
print('Free disk space {}MB'.format(free_space_mb))
|
||||
|
||||
print(f'Free disk space {free_space_mb}MB')
|
||||
|
||||
if free_space_mb < LOW_EBS_DISK_SPACE_LIMIT:
|
||||
print('Volume is running below EBS free disk space treshhold {}MB. Recreating volume and running clean build.'.format(LOW_EBS_DISK_SPACE_LIMIT))
|
||||
unmount_volume()
|
||||
detach_volume(volume, ec2_instance_id, False)
|
||||
print(f'Volume is running below EBS free disk space treshhold {LOW_EBS_DISK_SPACE_LIMIT}MB. Recreating volume and running clean build.')
|
||||
unmount_volume_from_device()
|
||||
detach_volume_from_ec2_instance(volume, ec2_instance_id, False)
|
||||
delete_volume(ec2_client, volume_id)
|
||||
new_disk_size = int(volume.size * 1.25)
|
||||
if new_disk_size > MAX_EBS_DISK_SIZE:
|
||||
print('Error: EBS disk size reached to the allowed maximum disk size {}MB, please contact ly-infra@ and ly-build@ to investigate.'.format(MAX_EBS_DISK_SIZE))
|
||||
print(f'Error: EBS disk size reached to the allowed maximum disk size {MAX_EBS_DISK_SIZE}MB, please contact ly-infra@ and ly-build@ to investigate.')
|
||||
exit(1)
|
||||
print('Recreating the EBS with disk size {}'.format(new_disk_size))
|
||||
volume_id, created = create_volume(ec2_client, ec2_availability_zone, repository_name, project, pipeline, branch, platform, build_type, new_disk_size, disk_type)
|
||||
print(f'Recreating the EBS with disk size {new_disk_size}')
|
||||
volume_id, created = create_volume(ec2_client, ec2_availability_zone, repository_name, project, pipeline,
|
||||
branch, platform, build_type, new_disk_size, disk_type)
|
||||
volume = ec2_resource.Volume(volume_id)
|
||||
attach_ebs_and_create_partition_with_retry(volume, volume_id, ec2_instance_id, created)
|
||||
attach_volume_to_ec2_instance(volume, volume_id, ec2_instance_id)
|
||||
mount_volume_to_device(created)
|
||||
|
||||
|
||||
def unmount_ebs():
|
||||
session = boto3.session.Session()
|
||||
region = session.region_name
|
||||
if region is None:
|
||||
region = DEFAULT_REGION
|
||||
ec2_client = get_ec2_client(region)
|
||||
region = get_region_name()
|
||||
ec2_instance_id = get_ec2_instance_id()
|
||||
ec2_resource = boto3.resource('ec2', region_name=region)
|
||||
ec2_instance = ec2_resource.Instance(ec2_instance_id)
|
||||
@@ -454,7 +475,7 @@ def unmount_ebs():
|
||||
|
||||
for attached_volume in ec2_instance.volumes.all():
|
||||
for attachment in attached_volume.attachments:
|
||||
print('attachment device: {}'.format(attachment['Device']))
|
||||
print(f"attachment device: {attachment['Device']}")
|
||||
if attachment['Device'] == 'xvdf':
|
||||
volume = attached_volume
|
||||
|
||||
@@ -462,24 +483,18 @@ def unmount_ebs():
|
||||
# volume is not mounted
|
||||
print('Volume is not mounted')
|
||||
else:
|
||||
unmount_volume()
|
||||
detach_volume(volume, ec2_instance_id, False)
|
||||
unmount_volume_from_device()
|
||||
detach_volume_from_ec2_instance(volume, ec2_instance_id, False)
|
||||
|
||||
|
||||
def delete_ebs(repository_name, project, pipeline, branch, platform, build_type):
|
||||
unmount_ebs()
|
||||
|
||||
session = boto3.session.Session()
|
||||
region = session.region_name
|
||||
if region is None:
|
||||
region = DEFAULT_REGION
|
||||
region = get_region_name()
|
||||
ec2_client = get_ec2_client(region)
|
||||
ec2_instance_id = get_ec2_instance_id()
|
||||
ec2_resource = boto3.resource('ec2', region_name=region)
|
||||
ec2_instance = ec2_resource.Instance(ec2_instance_id)
|
||||
|
||||
mount_name = get_mount_name(repository_name, project, pipeline, branch, platform, build_type)
|
||||
response = ec2_client.describe_volumes(Filters=[
|
||||
{ 'Name': 'tag:Name', 'Values': [mount_name] }
|
||||
{'Name': 'tag:Name', 'Values': [mount_name]}
|
||||
])
|
||||
|
||||
if 'Volumes' in response and len(response['Volumes']):
|
||||
@@ -496,7 +511,8 @@ def main(action, repository_name, project, pipeline, branch, platform, build_typ
|
||||
elif action == 'delete':
|
||||
delete_ebs(repository_name, project, pipeline, branch, platform, build_type)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
ret = main(args.action, args.repository_name, args.project, args.pipeline, args.branch, args.platform, args.build_type, args.disk_size, args.disk_type)
|
||||
sys.exit(ret)
|
||||
main(args.action, args.repository_name, args.project, args.pipeline, args.branch, args.platform,
|
||||
args.build_type, args.disk_size, args.disk_type)
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
import argparse
|
||||
import fnmatch
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
class LicenseScanner:
|
||||
"""Class to contain license scanner.
|
||||
|
||||
Scans source tree for license files using provided filename patterns and generates a file
|
||||
with the contents of all the licenses.
|
||||
|
||||
:param config_file: Config file with license patterns and scanner settings
|
||||
"""
|
||||
|
||||
DEFAULT_CONFIG_FILE = 'scanner_config.json'
|
||||
|
||||
def __init__(self, config_file=None):
|
||||
self.config_file = config_file
|
||||
self.config_data = self._load_config()
|
||||
self.license_regex = self._load_license_regex()
|
||||
|
||||
def _load_config(self):
|
||||
"""Load config from the provided file. Sets default file if one is not provided."""
|
||||
if self.config_file is None:
|
||||
script_directory = os.path.dirname(os.path.abspath(__file__)) # Default file expected in same dir as script
|
||||
self.config_file = os.path.join(script_directory, self.DEFAULT_CONFIG_FILE)
|
||||
|
||||
try:
|
||||
with open(self.config_file) as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
print('Config file cannot be found')
|
||||
raise
|
||||
|
||||
def _load_license_regex(self):
|
||||
"""Returns regex object with case-insensitive matching from the list of filename patterns."""
|
||||
regex_patterns = []
|
||||
for pattern in self.config_data['license_patterns']:
|
||||
regex_patterns.append(fnmatch.translate(pattern))
|
||||
return re.compile('|'.join(regex_patterns), re.IGNORECASE)
|
||||
|
||||
def scan(self, path=os.curdir):
|
||||
"""Scan directory tree for filenames matching license_regex.
|
||||
|
||||
:param path: Path of the directory to run scanner
|
||||
:return: Package paths and their corresponding license file contents
|
||||
:rtype: dict
|
||||
"""
|
||||
licenses = 0
|
||||
license_files = {}
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(path):
|
||||
for file in filenames:
|
||||
if self.license_regex.match(file):
|
||||
license_file_content = self._get_license_file_contents(os.path.join(dirpath, file))
|
||||
rel_dirpath = os.path.relpath(dirpath, path) # Limit path inside scanned directory
|
||||
license_files[rel_dirpath] = license_file_content
|
||||
licenses += 1
|
||||
print(f'License file: {os.path.join(dirpath, file)}')
|
||||
|
||||
# Remove directories that should not be scanned
|
||||
for dir in self.config_data['excluded_directories']:
|
||||
if dir in dirnames:
|
||||
dirnames.remove(dir)
|
||||
print(f'{licenses} license files found.')
|
||||
return license_files
|
||||
|
||||
def _get_license_file_contents(self, filepath):
|
||||
try:
|
||||
with open(filepath, encoding='utf8') as f:
|
||||
return f.read()
|
||||
except UnicodeDecodeError:
|
||||
print(f'Unable to read license file: {filepath}')
|
||||
pass
|
||||
|
||||
def create_license_file(self, licenses, filepath='NOTICES.txt'):
|
||||
"""Creates file with all the provided license file contents.
|
||||
|
||||
:param licenses: Dict with package paths and their corresponding license file contents
|
||||
:param filepath: Path to write the file
|
||||
"""
|
||||
package_separator = '------------------------------------'
|
||||
with open(filepath, 'w', encoding='utf8') as f:
|
||||
for directory, license in licenses.items():
|
||||
license_output = '\n\n'.join([
|
||||
f'{package_separator}',
|
||||
f'Package path: {directory}',
|
||||
'License:',
|
||||
f'{license}\n'
|
||||
])
|
||||
f.write(license_output)
|
||||
return None
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Script to run LicenseScanner and generate license file')
|
||||
parser.add_argument('--config-file', '-c', type=pathlib.Path, help='Config file for LicenseScanner')
|
||||
parser.add_argument('--license-file-path', '-l', type=pathlib.Path, help='Create license file in the provided path')
|
||||
parser.add_argument('--scan-path', '-s', default=os.curdir, type=pathlib.Path, help='Path to scan')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
args = parse_args()
|
||||
ls = LicenseScanner(args.config_file)
|
||||
licenses = ls.scan(args.scan_path)
|
||||
|
||||
if args.license_file_path:
|
||||
ls.create_license_file(licenses, args.license_file_path)
|
||||
except FileNotFoundError as e:
|
||||
print(f'Type: {type(e).__name__}, Error: {e}')
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"excluded_directories": [
|
||||
".git",
|
||||
".venv",
|
||||
"build",
|
||||
"license_scanner"
|
||||
],
|
||||
"license_patterns": [
|
||||
"LICENSE*",
|
||||
"COPYING*"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user