@@ -23,6 +23,25 @@ static bool IsUsablePrimaryCachePath(const std::string& path)
2323 && std::filesystem::is_regular_file (path, ec);
2424}
2525
26+
27+ static std::string PathRelativeTo (const std::string& path, const std::string& basePath)
28+ {
29+ std::error_code ec;
30+ auto relativePath = std::filesystem::relative (path, basePath, ec);
31+ auto relativePathString = relativePath.generic_string ();
32+ if (ec || relativePath.empty () || relativePathString == " .." || relativePathString.find (" ../" ) == 0 )
33+ return path;
34+ return relativePath.string ();
35+ }
36+
37+
38+ static std::string ResolveRelativePath (const std::string& path, const std::string& basePath)
39+ {
40+ if (path.empty () || std::filesystem::path (path).is_absolute ())
41+ return path;
42+ return (std::filesystem::path (basePath) / path).string ();
43+ }
44+
2645SharedCacheViewType::SharedCacheViewType () : BinaryViewType(VIEW_NAME , VIEW_NAME ) {}
2746
2847// We register all our one-shot stuff here, such as the object destructor.
@@ -852,17 +871,16 @@ bool SharedCacheView::InitController()
852871 }
853872 std::string primaryFileDir = std::filesystem::path (*primaryFilePath).parent_path ().string ();
854873
855- // Get the primary project file from the current files project.
856- // This is required to allow selecting a primary file in a different directory. Otherwise, we search the current database directory .
874+ // If the primary file is in the current project, use its project folder to discover related cache files .
875+ // Otherwise, fall back to scanning the resolved primary file's directory on disk .
857876 Ref<ProjectFile> primaryProjectFile = nullptr ;
858877 auto currentProjectFile = GetFile ()->GetProjectFile ();
859878 if (currentProjectFile)
860879 primaryProjectFile = currentProjectFile->GetProject ()->GetFileByPathOnDisk (*primaryFilePath);
861880
862881 if (!IsSameFolderForFile (primaryProjectFile, currentProjectFile))
863882 {
864- // TODO: Remove this restriction using stored cache UUID's and a fast project file search.
865- m_logger->LogWarn (" Because the primary file is in a different project folder you will need to select it on every open, consider moving the database file into the same folder." );
883+ m_logger->LogWarn (" The primary shared cache file is not in the same project folder as this database. Related cache files will be resolved from the primary file's project folder or directory." );
866884 }
867885
868886 // OK, we have the primary shared cache file, now let's add the entries.
@@ -1014,6 +1032,15 @@ void SharedCacheView::OnAfterSnapshotDataApplied()
10141032
10151033void SharedCacheView::SetPrimaryFileName (std::string primaryFileName)
10161034{
1035+ m_primaryFilePath.clear ();
1036+ m_primaryFileName = std::move (primaryFileName);
1037+ GetParentView ()->StoreMetadata (VIEW_METADATA_KEY , GetMetadata ());
1038+ }
1039+
1040+
1041+ void SharedCacheView::SetPrimaryFileLocation (std::string primaryFilePath, std::string primaryFileName)
1042+ {
1043+ m_primaryFilePath = std::move (primaryFilePath);
10171044 m_primaryFileName = std::move (primaryFileName);
10181045 GetParentView ()->StoreMetadata (VIEW_METADATA_KEY , GetMetadata ());
10191046}
@@ -1027,6 +1054,104 @@ void SharedCacheView::LogSecondaryFileName(std::string secondaryFileName)
10271054std::optional<std::string> SharedCacheView::GetPrimaryFilePath ()
10281055{
10291056 auto viewFile = GetFile ();
1057+ auto databaseDir = std::filesystem::path (viewFile->GetFilename ()).parent_path ().string ();
1058+ auto currentProjectFile = viewFile->GetProjectFile ();
1059+ Ref<Project> project = nullptr ;
1060+ if (currentProjectFile)
1061+ project = currentProjectFile->GetProject ();
1062+
1063+ auto storePrimaryProjectFile = [&](ProjectFile* projectFile) -> std::string {
1064+ SetPrimaryFileLocation (projectFile->GetPathInProject (), projectFile->GetName ());
1065+ return projectFile->GetPathOnDisk ();
1066+ };
1067+
1068+ auto storePrimaryFilePath = [&](const std::string& path) {
1069+ if (project)
1070+ {
1071+ if (auto projectFile = project->GetFileByPathOnDisk (path))
1072+ {
1073+ SetPrimaryFileLocation (projectFile->GetPathInProject (), projectFile->GetName ());
1074+ return ;
1075+ }
1076+
1077+ m_logger->LogWarnF (
1078+ " Primary shared cache file '{}' is outside the current project. Add the shared cache files to the project to avoid selecting them on future opens." ,
1079+ path);
1080+ SetPrimaryFileName (BaseFileName (path));
1081+ return ;
1082+ }
1083+
1084+ SetPrimaryFileLocation (PathRelativeTo (path, databaseDir), BaseFileName (path));
1085+ };
1086+
1087+ auto resolveProjectFilePath = [&](const std::string& projectPath) -> std::optional<std::string> {
1088+ if (!project || projectPath.empty ())
1089+ return std::nullopt ;
1090+
1091+ try
1092+ {
1093+ auto matches = project->GetFilesByPathInProject (projectPath);
1094+ if (matches.size () > 1 )
1095+ {
1096+ m_logger->LogErrorF (
1097+ " Multiple project files match primary shared cache path '{}'. Provide loader.dsc.primaryFilePath with an unambiguous project path." ,
1098+ projectPath);
1099+ return std::string ();
1100+ }
1101+ if (matches.size () == 1 )
1102+ return storePrimaryProjectFile (matches[0 ]);
1103+ }
1104+ catch (const std::exception& e)
1105+ {
1106+ m_logger->LogWarnForExceptionF (e, " Failed to resolve primary shared cache project path '{}': {}" , projectPath,
1107+ e.what ());
1108+ }
1109+
1110+ return std::nullopt ;
1111+ };
1112+
1113+ auto resolveUniqueProjectFileName = [&]() -> std::optional<std::string> {
1114+ if (!project || m_primaryFileName.empty ())
1115+ return std::nullopt ;
1116+
1117+ std::vector<Ref<ProjectFile>> matches;
1118+ for (const auto & projectFile : project->GetFiles ())
1119+ if (projectFile->GetName () == m_primaryFileName)
1120+ matches.push_back (projectFile);
1121+
1122+ if (matches.empty ())
1123+ return std::nullopt ;
1124+
1125+ if (matches.size () > 1 )
1126+ {
1127+ std::string paths;
1128+ for (const auto & match : matches)
1129+ {
1130+ if (!paths.empty ())
1131+ paths += " , " ;
1132+ paths += match->GetPathInProject ();
1133+ }
1134+ m_logger->LogErrorF (
1135+ " Multiple project files are named '{}': {}. Provide loader.dsc.primaryFilePath with the project path to the correct primary shared cache file." ,
1136+ m_primaryFileName, paths);
1137+ return std::string ();
1138+ }
1139+
1140+ return storePrimaryProjectFile (matches[0 ]);
1141+ };
1142+
1143+ auto resolveMetadataPrimaryFilePath = [&]() -> std::optional<std::string> {
1144+ if (m_primaryFilePath.empty ())
1145+ return std::nullopt ;
1146+
1147+ if (project)
1148+ return resolveProjectFilePath (m_primaryFilePath);
1149+
1150+ auto path = ResolveRelativePath (m_primaryFilePath, databaseDir);
1151+ if (IsUsablePrimaryCachePath (path))
1152+ return path;
1153+ return std::nullopt ;
1154+ };
10301155
10311156 auto settings = GetLoadSettings (GetTypeName ());
10321157 if (settings && settings->Contains (" loader.dsc.primaryFilePath" ))
@@ -1035,7 +1160,17 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
10351160 if (!configuredPrimaryFilePath.empty ())
10361161 {
10371162 settings->Reset (" loader.dsc.primaryFilePath" , this , SettingsResourceScope);
1038- if (!IsUsablePrimaryCachePath (configuredPrimaryFilePath))
1163+ if (project)
1164+ {
1165+ auto projectPathResult = resolveProjectFilePath (configuredPrimaryFilePath);
1166+ if (projectPathResult && projectPathResult->empty ())
1167+ return std::nullopt ;
1168+ if (projectPathResult)
1169+ return *projectPathResult;
1170+ }
1171+
1172+ auto resolvedConfiguredPath = ResolveRelativePath (configuredPrimaryFilePath, databaseDir);
1173+ if (!IsUsablePrimaryCachePath (resolvedConfiguredPath))
10391174 {
10401175 m_logger->LogErrorF (
10411176 " Configured primary shared cache file path is invalid: '{}'" , configuredPrimaryFilePath);
@@ -1044,8 +1179,8 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
10441179 }
10451180 else
10461181 {
1047- SetPrimaryFileName (BaseFileName (configuredPrimaryFilePath ));
1048- return configuredPrimaryFilePath ;
1182+ SetPrimaryFileName (BaseFileName (resolvedConfiguredPath ));
1183+ return resolvedConfiguredPath ;
10491184 }
10501185 }
10511186 }
@@ -1087,10 +1222,17 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
10871222 return newPrimaryFilePath;
10881223 };
10891224
1090- // 1. Try and get the primary file path using `GetOriginalFilename`.
1225+ if (auto metadataPrimaryPath = resolveMetadataPrimaryFilePath ())
1226+ {
1227+ if (metadataPrimaryPath->empty ())
1228+ return std::nullopt ;
1229+ return *metadataPrimaryPath;
1230+ }
1231+
1232+ // 1. Try the original filename for existing databases and direct opens of the primary file.
10911233 auto primaryFilePath = viewFile->GetOriginalFilename ();
10921234
1093- // 2. If the original file name is not a usable file path then prompt the user to select one .
1235+ // 2. If the original filename is stale, try nearby files using stored metadata and legacy filename hints .
10941236 if (!IsUsablePrimaryCachePath (primaryFilePath))
10951237 {
10961238 std::vector<std::string> candidateNames;
@@ -1102,7 +1244,7 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
11021244
11031245 for (const auto & candidateName : candidateNames)
11041246 {
1105- auto candidatePath = (std::filesystem::path (viewFile-> GetFilename ()). parent_path ( ) / candidateName).string ();
1247+ auto candidatePath = (std::filesystem::path (databaseDir ) / candidateName).string ();
11061248 if (IsUsablePrimaryCachePath (candidatePath))
11071249 {
11081250 primaryFilePath = candidatePath;
@@ -1126,28 +1268,25 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
11261268 return std::nullopt ;
11271269 }
11281270
1129- SetPrimaryFileName ( BaseFileName ( primaryFilePath) );
1271+ storePrimaryFilePath ( primaryFilePath);
11301272 }
11311273
1132- // 3. If we are not in a project, we can go ahead and return the file path, it does not need to be resolved from project.
1133- auto primaryProjectFile = viewFile->GetProjectFile ();
1134- if (!primaryProjectFile)
1274+ // 3. If we are not in a project, the filesystem path is ready to use.
1275+ if (!currentProjectFile)
11351276 return primaryFilePath;
11361277
1137- auto project = primaryProjectFile->GetProject ();
1138- auto primaryProjectFileName = primaryProjectFile->GetName ();
1139- auto primaryProjectFilePath = primaryProjectFile->GetPathOnDisk ();
1278+ auto primaryProjectFileName = currentProjectFile->GetName ();
1279+ auto primaryProjectFilePath = currentProjectFile->GetPathOnDisk ();
11401280
1141- // 4. If we are not a BNDB project file than we can return the path on disk as we are the primary file .
1281+ // 4. If the project file is the primary cache itself, use it and persist its project path .
11421282 if (primaryProjectFileName.find (" .bndb" ) == std::string::npos)
11431283 {
1144- // Set the primary file name to the project file name so on subsequent loads we can pick it up.
1145- SetPrimaryFileName (primaryProjectFileName);
1284+ SetPrimaryFileLocation (currentProjectFile->GetPathInProject (), primaryProjectFileName);
11461285 return primaryProjectFilePath;
11471286 }
11481287
1149- // 5. If we are a BNDB project file the path must be resolved from the file name .
1150- auto primaryProjectFileFolder = primaryProjectFile ->GetFolder ();
1288+ // 5. Prefer a primary cache file with the stored basename in the same project folder as the BNDB .
1289+ auto primaryProjectFileFolder = currentProjectFile ->GetFolder ();
11511290 for (const auto & pj : project->GetFiles ())
11521291 {
11531292 // Skip files not in the same folder.
@@ -1156,24 +1295,31 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
11561295 // We are looking for the file with file name we stored in metadata.
11571296 if (pj->GetName () != m_primaryFileName)
11581297 continue ;
1159- return pj->GetPathOnDisk ();
1298+ return storePrimaryProjectFile (pj);
1299+ }
1300+
1301+ if (auto uniqueProjectPath = resolveUniqueProjectFileName ())
1302+ {
1303+ if (uniqueProjectPath->empty ())
1304+ return std::nullopt ;
1305+ return *uniqueProjectPath;
11601306 }
11611307
11621308 if (IsUsablePrimaryCachePath (primaryFilePath) && BaseFileName (primaryFilePath) == m_primaryFileName)
11631309 return primaryFilePath;
11641310
1165- // 6. If we fail to resolve the project file given the `m_primaryFileName` than we fall back to asking the user.
1311+ // 6. If automatic project resolution failed, ask the user in UI mode. Headless callers must provide
1312+ // loader.dsc.primaryFilePath or arrange files so one of the automatic resolution paths works.
11661313 auto promptedPrimaryFilePath = promptForPrimaryFile ();
11671314 if (!promptedPrimaryFilePath)
11681315 return std::nullopt ;
11691316 std::string newPrimaryFilePath = *promptedPrimaryFilePath;
11701317
1171- // TODO: We likely want to verify that the project file exists in the same directory as the BNDB.
1172- // TODO: We currently require the database to exist in the same directory as the files.
1173- // Update the primary file name for later loads, otherwise we would keep prompting to select a file.
1174- primaryProjectFile = project->GetFileByPathOnDisk (newPrimaryFilePath);
1318+ // Persist a project-relative path when the selected file is in the project. External selections are
1319+ // allowed as an escape hatch, but only the basename is stored so local absolute paths are not synced.
1320+ auto primaryProjectFile = project->GetFileByPathOnDisk (newPrimaryFilePath);
11751321 if (primaryProjectFile)
1176- SetPrimaryFileName ( primaryProjectFile->GetName ());
1322+ SetPrimaryFileLocation (primaryProjectFile-> GetPathInProject (), primaryProjectFile->GetName ());
11771323 else
11781324 SetPrimaryFileName (BaseFileName (newPrimaryFilePath));
11791325 return newPrimaryFilePath;
@@ -1190,10 +1336,12 @@ Ref<Metadata> SharedCacheView::GetMetadata() const
11901336
11911337 // TODO: Refactor this to just "cache files" which is a new struct of:
11921338 // TODO: cache file name
1339+ // TODO: cache file path
11931340 // TODO: cache file UUID
11941341 // TODO: cache file entry type?
11951342 viewMeta[" secondaryFileNames" ] = new Metadata (secondaryFileNames);
11961343 viewMeta[" primaryFileName" ] = new Metadata (m_primaryFileName);
1344+ viewMeta[" primaryFilePath" ] = new Metadata (m_primaryFilePath);
11971345
11981346 return new Metadata (viewMeta);
11991347}
@@ -1210,4 +1358,6 @@ void SharedCacheView::LoadMetadata(const Metadata &metadata)
12101358
12111359 if (viewMeta.find (" primaryFileName" ) != viewMeta.end ())
12121360 m_primaryFileName = viewMeta[" primaryFileName" ]->GetString ();
1361+ if (viewMeta.find (" primaryFilePath" ) != viewMeta.end ())
1362+ m_primaryFilePath = viewMeta[" primaryFilePath" ]->GetString ();
12131363}
0 commit comments