diff --git a/IDE/src/ui/ProjectProperties.bf b/IDE/src/ui/ProjectProperties.bf index 02638e6c..aa728de0 100644 --- a/IDE/src/ui/ProjectProperties.bf +++ b/IDE/src/ui/ProjectProperties.bf @@ -811,7 +811,26 @@ namespace IDE.ui if (verSpecPtr == null) { if (verSpec case .Git(let url, let ver)) - updateProjectLock = true; + { + bool matchesWorkspace = false; + for (var projectSpec in gApp.mWorkspace.mProjectSpecs) + { + if (projectSpec.mProjectName == projectName) + { + if (projectSpec.mVerSpec case .Git(let checkURL, let checkVer)) + { + if ((checkURL == url) && (checkVer == ver)) + matchesWorkspace = true; + } + } + } + + if (!matchesWorkspace) + { + // This added new spec matches the workspace so we it doesn't affect any locking + updateProjectLock = true; + } + } var dep = new Project.Dependency(); dep.mProjectName = new String(listViewItem.mLabel); diff --git a/IDE/src/ui/WorkspaceProperties.bf b/IDE/src/ui/WorkspaceProperties.bf index 6438bb7d..bc117837 100644 --- a/IDE/src/ui/WorkspaceProperties.bf +++ b/IDE/src/ui/WorkspaceProperties.bf @@ -874,14 +874,40 @@ namespace IDE.ui for (var projectSpec in gApp.mWorkspace.mProjectSpecs) { + String bestConstraint = scope .(); + String bestURL = scope .(); + if (projectSpec.mProjectName == projectName) { if (projectSpec.mVerSpec case .Git(let url, let ver)) { - dependencyEntry.SetValue(1, url); - dependencyEntry.SetValue(2, ver.mVersion); + bestConstraint.Set(ver.mVersion); + bestURL.Set(url); } } + + for (var project in gApp.mWorkspace.mProjects) + { + for (var dep in project.mDependencies) + { + if (dep.mVerSpec case .Git(let url, let ver)) + { + if (bestURL.IsEmpty) + bestURL.Set(url); + else if (url != bestURL) + continue; + + String highConstraint = scope .(); + if ((ver.mVersion != null) && (SemVer.GetHighestConstraint(bestConstraint, ver.mVersion, highConstraint))) + { + bestConstraint.Set(highConstraint); + } + } + } + } + + dependencyEntry.SetValue(1, bestURL); + dependencyEntry.SetValue(2, bestConstraint); } var propEntries = mPropPage.mPropEntries[listViewItem]; UpdatePropertyValue(propEntries); diff --git a/IDE/src/util/PackMan.bf b/IDE/src/util/PackMan.bf index ab151543..09205b19 100644 --- a/IDE/src/util/PackMan.bf +++ b/IDE/src/util/PackMan.bf @@ -55,6 +55,12 @@ namespace IDE.util public bool CheckInit() { + if ((gApp.mWorkspace.mProjectLoadState != .Preparing) && (mWorkItems.IsEmpty)) + { + // Clear failed state + mFailed = false; + } + if (mInitialized) return true; @@ -215,7 +221,7 @@ namespace IDE.util workItem.mKind = .FindVersion; workItem.mProjectName = new .(projectName); workItem.mURL = new .(url); - if (semVer != null) + if (!semVer.IsEmpty) workItem.mConstraints = new .() { new String(semVer.mVersion) }; mWorkItems.Add(workItem); } @@ -287,8 +293,12 @@ namespace IDE.util for (var tag in workItem.mGitInstance.mTagInfos) { - if ((tag.mTag == "HEAD") && (workItem.mConstraints == null)) + if ((tag.mTag == "HEAD") && + ((workItem.mConstraints == null) || (workItem.mConstraints.Contains("HEAD")))) + { bestHash = tag.mHash; + break; + } else if (workItem.mConstraints != null) { bool hasMatch = false; @@ -328,7 +338,7 @@ namespace IDE.util constraints.Append('\''); } - Fail(scope $"Failed to locate version for '{workItem.mProjectName}' with constraints '{constraints}'"); + Fail(scope $"Failed to locate version for '{workItem.mProjectName}' with constraints {constraints}"); } case .Clone: Checkout(workItem.mProjectName, workItem.mURL, workItem.mPath, workItem.mTag, workItem.mHash); @@ -341,6 +351,10 @@ namespace IDE.util default: } } + else + { + Fail(scope $"Failed to retrieve project '{workItem.mProjectName}' at '{workItem.mURL}'"); + } @workItem.Remove(); delete workItem; diff --git a/IDE/src/util/SemVer.bf b/IDE/src/util/SemVer.bf index d319b94d..cb26cf36 100644 --- a/IDE/src/util/SemVer.bf +++ b/IDE/src/util/SemVer.bf @@ -1,4 +1,5 @@ using System; +using System.Collections; namespace IDE.Util { @@ -30,6 +31,17 @@ namespace IDE.Util public Kind Major => mPart[0]; public Kind Minor => mPart[1]; public Kind Patch => mPart[2]; + + public static int operator<=>(Self lhs, Self rhs) + { + for (int i < 3) + { + int val = lhs.mPart[i].NumOrDefault <=> rhs.mPart[i].NumOrDefault; + if (val != 0) + return val; + } + return 0; + } } enum CompareKind @@ -143,7 +155,13 @@ namespace IDE.Util return GetParts(mVersion); } - public static bool IsVersionMatch(StringView fullVersion, StringView wildcard) + enum VersionSpec + { + case String(StringView version); + case Parts(SemVer.Parts parts); + } + + static bool IsVersionMatch(VersionSpec fullVersion, StringView wildcard) { int commaPos = wildcard.IndexOf(','); if (commaPos != -1) @@ -193,13 +211,21 @@ namespace IDE.Util // Does we include equality? if ((compareKind != .Gt) && (compareKind != .Lt)) { - if (fullVersion == wildcard) - return true; + if (fullVersion case .String(let fullStr)) + if (fullStr == wildcard) + return true; } Parts full; - if (!(GetParts(fullVersion) case .Ok(out full))) - return false; + switch (fullVersion) + { + case .String(let fullStr): + if (!(GetParts(fullStr) case .Ok(out full))) + return false; + case .Parts(let fullParts): + full = fullParts; + } + Parts wild; if (!(GetParts(wildcard) case .Ok(out wild))) return false; @@ -265,6 +291,11 @@ namespace IDE.Util return true; } + public static bool IsVersionMatch(StringView fullVersion, StringView wildcard) + { + return IsVersionMatch(.String(fullVersion), wildcard); + } + public static bool IsVersionMatch(SemVer fullVersion, SemVer wildcard) => IsVersionMatch(fullVersion.mVersion, wildcard.mVersion); public static Result Compare(StringView lhs, StringView rhs) @@ -297,6 +328,145 @@ namespace IDE.Util return comp; } + static void FindEmbeddedVersion(StringView version, List parts, ref int32[3] highestParts) + { + int startIdx = 0; + int partIdx = 0; + int sectionCount = 0; + + if (version.IsEmpty) + return; + + Result SetPart(Parts.Kind kind) + { + if (partIdx >= 3) + return .Err; + int32 val = 0; + if (kind case .Num(out val)) {} + if (partIdx == 0) + parts.Add(default); + parts.Back[partIdx] = val; + highestParts[partIdx] = Math.Max(highestParts[partIdx], val); + partIdx++; + return .Ok; + } + + Result FlushPart(int i) + { + StringView partStr = version.Substring(startIdx, i - startIdx); + if (!partStr.IsEmpty) + { + int32 partNum = Try!(int32.Parse(partStr)); + Try!(SetPart(.Num(partNum))); + } + return .Ok; + } + + for (int i in startIdx ..< version.Length) + { + char8 c = version[i]; + if (c == '.') + { + FlushPart(i).IgnoreError(); + startIdx = i + 1; + continue; + } + else if (c.IsNumber) + { + continue; + } + else + { + startIdx = i + 1; + partIdx = 0; + sectionCount++; + } + + } + FlushPart(version.Length).IgnoreError(); + } + + public static bool GetHighestConstraint(StringView ver1, StringView ver2, String outVer) + { + if (ver1.IsEmpty) + { + if (ver2.IsEmpty) + return false; + outVer.Set(ver2); + return true; + } + + if (ver2.IsEmpty) + { + outVer.Set(ver1); + return true; + } + + List embeddedParts = scope .(); + int32[3] highestParts = default; + FindEmbeddedVersion(ver1, embeddedParts, ref highestParts); + FindEmbeddedVersion(ver2, embeddedParts, ref highestParts); + + StringView bestVer = default; + SemVer.Parts bestParts = default; + + bool CheckMatch(SemVer.Parts parts) + { + bool match1 = IsVersionMatch(.Parts(parts), ver1); + bool match2 = IsVersionMatch(.Parts(parts), ver2); + + StringView verMatch = default; + + if ((match1) && (!match2)) + verMatch = ver1; + else if ((match2) && (!match1)) + verMatch = ver2; + else + return false; + + if (parts <=> bestParts > 0) + { + bestParts = parts; + bestVer = verMatch; + } + return true; + } + + bool CheckParts(int32[3] parts) + { + SemVer.Parts checkParts = default; + for (int i < 3) + { + int32 val = parts[i]; + if (val < 0) + return false; + checkParts.mPart[i] = .Num(val); + } + return CheckMatch(checkParts); + } + + // Try variations of explicitly-stated versions in constraints and try to find versions that match one constraint but not another, + // then select the constraint that allowed the highest version in that the other constraint didn't + for (var parts in embeddedParts) + { + CheckParts(.(parts[0], parts[1], parts[2])); + CheckParts(.(parts[0], parts[1], parts[2] - 1)); + CheckParts(.(parts[0], parts[1], parts[2] + 1)); + CheckParts(.(parts[0], parts[1] + 1, 0)); + CheckParts(.(parts[0], parts[1] - 1, 999999)); + CheckParts(.(parts[0] + 1, 0, 0)); + CheckParts(.(parts[0] - 1, 999999, 999999)); + } + + if (!bestVer.IsEmpty) + { + outVer.Set(bestVer); + return true; + } + + return false; + } + public override void ToString(String strBuffer) { strBuffer.Append(mVersion);