Skip to content

Commit 92bf2e5

Browse files
authored
Install transitive dependencies of versions properly (#5745)
* install recursive deps properly * fix up
1 parent a6d359e commit 92bf2e5

1 file changed

Lines changed: 124 additions & 19 deletions

File tree

apps/app-frontend/src/store/install.js

Lines changed: 124 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import dayjs from 'dayjs'
44

5-
import { get_project, get_version_many } from '@/helpers/cache.js'
5+
import { get_project, get_version, get_version_many } from '@/helpers/cache.js'
66
import { add_project_from_version, check_installed } from '@/helpers/profile.js'
77
import {
88
add_server_to_profile,
@@ -49,33 +49,138 @@ export const isVersionCompatible = (version, project, instance) => {
4949
}
5050

5151
export const installVersionDependencies = async (profile, version, onDepInstalling) => {
52-
for (const dep of version.dependencies) {
53-
if (dep.dependency_type !== 'required') continue
54-
// disallow fabric api install on quilt
55-
if (dep.project_id === 'P7dR8mSH' && profile.loader === 'quilt') continue
52+
const projectNames = new Map()
53+
const storeProjectName = (p) => {
54+
if (p?.id && p.title) projectNames.set(p.id, p.title)
55+
}
56+
57+
const visitedVersions = new Set()
58+
const announcedProjects = new Set()
59+
const queuedVersionIds = new Set()
60+
const queuedProjectVersions = new Map()
61+
const queuedInstalls = []
62+
const installedProjectCache = new Map()
63+
64+
const isProjectInstalled = async (projectId) => {
65+
if (!projectId) return false
66+
if (installedProjectCache.has(projectId)) {
67+
return installedProjectCache.get(projectId)
68+
}
69+
const installed = await check_installed(profile.path, projectId)
70+
installedProjectCache.set(projectId, installed)
71+
return installed
72+
}
73+
74+
const queueInstall = async (projectId, resolvedVersion) => {
75+
if (!resolvedVersion?.id) return false
76+
77+
const versionId = resolvedVersion.id
78+
const resolvedProjectId = projectId ?? resolvedVersion.project_id ?? null
79+
80+
if (resolvedProjectId) {
81+
if (await isProjectInstalled(resolvedProjectId)) return false
82+
83+
const existingVersionId = queuedProjectVersions.get(resolvedProjectId)
84+
if (existingVersionId && existingVersionId !== versionId) return false
85+
if (existingVersionId === versionId) return false
86+
}
87+
88+
if (queuedVersionIds.has(versionId)) return false
89+
90+
queuedVersionIds.add(versionId)
91+
if (resolvedProjectId) {
92+
queuedProjectVersions.set(resolvedProjectId, versionId)
93+
}
94+
queuedInstalls.push({ versionId, projectId: resolvedProjectId })
95+
return true
96+
}
97+
98+
const announceDependency = async (projectId, resolvedVersion) => {
99+
if (!onDepInstalling || !projectId) return
100+
if (announcedProjects.has(projectId)) return
101+
102+
const depProject = await get_project(projectId, 'bypass').catch(() => null)
103+
if (!depProject) return
104+
105+
storeProjectName(depProject)
106+
onDepInstalling(depProject, resolvedVersion ?? undefined)
107+
announcedProjects.add(projectId)
108+
}
109+
110+
const resolveDependency = async (dep) => {
111+
let depVersion = null
112+
let depProjectId = dep.project_id ?? null
113+
56114
if (dep.version_id) {
57-
if (dep.project_id && (await check_installed(profile.path, dep.project_id))) continue
58-
if (dep.project_id && onDepInstalling) {
59-
const depProject = await get_project(dep.project_id, 'bypass').catch(() => null)
60-
if (depProject) onDepInstalling(depProject)
115+
depVersion = await get_version(dep.version_id, 'bypass').catch(() => null)
116+
if (!depVersion) return null
117+
118+
depProjectId = depProjectId ?? depVersion.project_id ?? null
119+
if (depProjectId && !projectNames.has(depProjectId)) {
120+
const p = await get_project(depProjectId, 'bypass').catch(() => null)
121+
storeProjectName(p)
61122
}
62-
await add_project_from_version(profile.path, dep.version_id)
63-
} else {
64-
if (dep.project_id && (await check_installed(profile.path, dep.project_id))) continue
123+
} else if (dep.project_id) {
124+
const depProject = await get_project(dep.project_id, 'bypass').catch(() => null)
125+
if (!depProject) return null
65126

66-
const depProject = await get_project(dep.project_id, 'bypass')
67-
if (onDepInstalling) onDepInstalling(depProject)
127+
storeProjectName(depProject)
68128

69-
const depVersions = (await get_version_many(depProject.versions, 'bypass')).sort(
70-
(a, b) => dayjs(b.date_published) - dayjs(a.date_published),
129+
const depVersions = await get_version_many(depProject.versions, 'bypass').catch(() => [])
130+
depVersion = findPreferredVersion(
131+
depVersions.sort((a, b) => dayjs(b.date_published) - dayjs(a.date_published)),
132+
dep,
133+
profile,
71134
)
135+
if (!depVersion) return null
136+
137+
depProjectId = dep.project_id
138+
} else {
139+
return null
140+
}
141+
142+
return { depVersion, depProjectId }
143+
}
144+
145+
const collectDependenciesForVersion = async (inputVersion) => {
146+
if (!inputVersion?.id || visitedVersions.has(inputVersion.id)) return
147+
visitedVersions.add(inputVersion.id)
72148

73-
const latest = findPreferredVersion(depVersions, dep, profile)
74-
if (latest) {
75-
await add_project_from_version(profile.path, latest.id)
149+
if (inputVersion.project_id && !projectNames.has(inputVersion.project_id)) {
150+
const p = await get_project(inputVersion.project_id, 'bypass').catch(() => null)
151+
storeProjectName(p)
152+
}
153+
154+
for (const dep of inputVersion.dependencies ?? []) {
155+
if (dep.dependency_type !== 'required') continue
156+
if (dep.project_id === 'P7dR8mSH' && profile.loader === 'quilt') continue
157+
158+
const resolved = await resolveDependency(dep, inputVersion)
159+
if (!resolved) continue
160+
161+
const { depVersion, depProjectId } = resolved
162+
const queued = await queueInstall(depProjectId, depVersion)
163+
if (queued && depProjectId) {
164+
await announceDependency(depProjectId, depVersion)
76165
}
166+
167+
await collectDependenciesForVersion(depVersion)
77168
}
78169
}
170+
171+
await collectDependenciesForVersion(version)
172+
173+
if (queuedInstalls.length === 0) return
174+
175+
const batchSize = 8
176+
for (let i = 0; i < queuedInstalls.length; i += batchSize) {
177+
const batch = queuedInstalls.slice(i, i + batchSize)
178+
await Promise.all(
179+
batch.map(async ({ versionId }) => {
180+
await add_project_from_version(profile.path, versionId)
181+
}),
182+
)
183+
}
79184
}
80185

81186
export const getServerAddress = (javaServer) => {

0 commit comments

Comments
 (0)