33var BuildsCacher = module . exports ;
44
55let Fs = require ( 'node:fs/promises' ) ;
6+ let Os = require ( 'node:os' ) ;
67let Path = require ( 'node:path' ) ;
78
9+ let LEGACY_CACHE_DIR = Path . join ( Os . homedir ( ) , '.cache/webi/legacy' ) ;
10+
811let HostTargets = require ( './build-classifier/host-targets.js' ) ;
912let Lexver = require ( './build-classifier/lexver.js' ) ;
1013let Triplet = require ( './build-classifier/triplet.js' ) ;
1114
12- var ALIAS_RE = / ^ a l i a s : ( \w + ) $ / m;
15+ var ALIAS_RE = / ^ a l i a s : ( [ \w . - ] + ) $ / m;
1316
1417var LEGACY_ARCH_MAP = {
1518 '*' : 'ANYARCH' ,
@@ -126,61 +129,8 @@ async function readFirstBytes(path) {
126129 return str ;
127130}
128131
129- let promises = { } ;
130- async function getLatestBuilds ( Releases , installersDir , cacheDir , name , date ) {
131- console . info ( `[INFO] getLatestBuilds: ${ name } ` ) ;
132-
133- if ( ! Releases ) {
134- Releases = require ( `${ installersDir } /${ name } /releases.js` ) ;
135- }
136- // TODO update all releases files with module.exports.xxxx = 'foo';
137- if ( ! Releases . latest ) {
138- Releases . latest = Releases ;
139- }
140-
141- let id = `${ cacheDir } /${ name } ` ;
142- if ( ! promises [ id ] ) {
143- promises [ id ] = Promise . resolve ( ) ;
144- }
145-
146- promises [ id ] = promises [ id ] . then ( async function ( ) {
147- return await getLatestBuildsInner ( Releases , cacheDir , name , date ) ;
148- } ) ;
149-
150- return await promises [ id ] ;
151- }
152-
153- async function getLatestBuildsInner ( Releases , cacheDir , name , date ) {
154- let data = await Releases . latest ( ) ;
155-
156- if ( ! date ) {
157- date = new Date ( ) ;
158- }
159- let isoDate = date . toISOString ( ) ;
160- let yearMonth = isoDate . slice ( 0 , 7 ) ;
161-
162- // TODO hash file
163- let dataFile = `${ cacheDir } /${ yearMonth } /${ name } .json` ;
164- // TODO fsstat releases.js vs require-ing time as well
165- let tsFile = `${ cacheDir } /${ yearMonth } /${ name } .updated.txt` ;
166-
167- let dirPath = Path . dirname ( dataFile ) ;
168- await Fs . mkdir ( dirPath , { recursive : true } ) ;
169-
170- let json = JSON . stringify ( data , null , 2 ) ;
171- await Fs . writeFile ( dataFile , json , 'utf8' ) ;
172-
173- let seconds = date . valueOf ( ) ;
174- let ms = seconds / 1000 ;
175- let msStr = ms . toFixed ( 3 ) ;
176- await Fs . writeFile ( tsFile , msStr , 'utf8' ) ;
177-
178- return data ;
179- }
180-
181- BuildsCacher . create = function ( { ALL_TERMS , installers, caches } ) {
132+ BuildsCacher . create = function ( { ALL_TERMS , installers } ) {
182133 let installersDir = installers ;
183- let cacheDir = caches ;
184134
185135 if ( ! ALL_TERMS ) {
186136 ALL_TERMS = Triplet . TERMS_PRIMARY_MAP ;
@@ -195,7 +145,6 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
195145 bc . _triplets = { } ;
196146 bc . _targetsByBuildIdCache = { } ;
197147 bc . _caches = { } ;
198- bc . _staleAge = 15 * 60 * 1000 ;
199148 bc . _allFormats = { } ;
200149 bc . _allTriplets = { } ;
201150 // Per-name lock: serializes cold-cache getPackages so concurrent
@@ -219,19 +168,6 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
219168 let entries = await Fs . readdir ( installersDir , { withFileTypes : true } ) ;
220169 for ( let entry of entries ) {
221170 let meta = await bc . getProjectTypeByEntry ( entry ) ;
222- if ( meta . type === 'not_found' ) {
223- let err = meta . detail ;
224- console . error ( '' ) ;
225- console . error ( 'PROBLEM' ) ;
226- console . error ( ` ${ err . message } ` ) ;
227- console . error ( '' ) ;
228- console . error ( 'SOLUTION' ) ;
229- console . error ( ' npm clean-install' ) ;
230- console . error ( '' ) ;
231- throw new Error (
232- '[SANITY FAIL] should never have missing modules in prod' ,
233- ) ;
234- }
235171 dirs [ meta . type ] [ entry . name ] = meta . detail ;
236172 }
237173
@@ -300,19 +236,17 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
300236 return { type : 'alias' , detail : link } ;
301237 }
302238
303- let releasesPath = Path . join ( path , 'releases.js' ) ;
304- try {
305- void require ( releasesPath ) ;
306- } catch ( err ) {
307- if ( err . code !== 'MODULE_NOT_FOUND' ) {
308- return { type : 'errors' , detail : err } ;
309- }
310-
311- if ( err . message . includes ( `Cannot find module '${ releasesPath } '` ) ) {
312- return { type : 'selfhosted' , detail : true } ;
313- }
314-
315- return { type : 'not_found' , detail : err } ;
239+ let cacheFile = `${ LEGACY_CACHE_DIR } /${ entry . name } .json` ;
240+ let hasCacheFile = await Fs . access ( cacheFile ) . then (
241+ function ( ) {
242+ return true ;
243+ } ,
244+ function ( ) {
245+ return false ;
246+ } ,
247+ ) ;
248+ if ( ! hasCacheFile ) {
249+ return { type : 'selfhosted' , detail : true } ;
316250 }
317251
318252 return { type : 'valid' , detail : true } ;
@@ -337,14 +271,9 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
337271 return p ;
338272 } ;
339273
340- async function _doGetPackages ( { Releases, name, date } ) {
341- if ( ! date ) {
342- date = new Date ( ) ;
343- }
344- let isoDate = date . toISOString ( ) ;
345- let yearMonth = isoDate . slice ( 0 , 7 ) ;
346- let dataFile = `${ cacheDir } /${ yearMonth } /${ name } .json` ;
347- let tsFile = `${ cacheDir } /${ yearMonth } /${ name } .updated.txt` ;
274+ async function _doGetPackages ( { name } ) {
275+ let dataFile = `${ LEGACY_CACHE_DIR } /${ name } .json` ;
276+ let tsFile = `${ LEGACY_CACHE_DIR } /${ name } .updated.txt` ;
348277
349278 let tsDate ;
350279 {
@@ -398,7 +327,7 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
398327 }
399328 }
400329 if ( ! projInfo ) {
401- projInfo = await getLatestBuilds ( Releases , installersDir , cacheDir , name ) ;
330+ return meta ;
402331 }
403332 let latestProjInfo = await BuildsCacher . transformAndUpdate (
404333 name ,
@@ -409,64 +338,9 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
409338 ) ;
410339 bc . _caches [ name ] = latestProjInfo ;
411340
412- process . nextTick ( async function ( ) {
413- let now = date . valueOf ( ) ;
414- let age = now - projInfo . updated ;
415-
416- let fresh = age < bc . _staleAge ;
417- if ( fresh ) {
418- return ;
419- }
420-
421- projInfo = await getLatestBuilds ( Releases , installersDir , cacheDir , name ) ;
422- let latestProjInfo = BuildsCacher . transformAndUpdate (
423- name ,
424- projInfo ,
425- meta ,
426- date ,
427- bc ,
428- ) ;
429- bc . _caches [ name ] = latestProjInfo ;
430- } ) ;
431-
432- return projInfo ;
341+ return latestProjInfo ;
433342 }
434343
435- // Makes sure that packages are updated once an hour, on average
436- bc . _staleNames = [ ] ;
437- bc . _freshenTimeout = null ;
438- bc . freshenRandomPackage = async function ( minDelay ) {
439- if ( ! minDelay ) {
440- minDelay = 15 * 1000 ;
441- }
442-
443- if ( bc . _staleNames . length === 0 ) {
444- let dirs = await bc . getProjectsByType ( ) ;
445- bc . _staleNames = Object . keys ( dirs . valid ) ;
446- bc . _staleNames . sort ( function ( ) {
447- return 0.5 - Math . random ( ) ;
448- } ) ;
449- }
450-
451- let name = bc . _staleNames . pop ( ) ;
452- void ( await bc . getPackages ( {
453- //Releases: Releases,
454- name : name ,
455- date : new Date ( ) ,
456- } ) ) ;
457- console . info ( `[INFO] freshenRandomPackage: ${ name } ` ) ;
458-
459- let hour = 60 * 60 * 1000 ;
460- let delay = minDelay ;
461- let spread = hour / bc . _staleNames . length ;
462- let seed = Math . random ( ) ;
463- delay += seed * spread ;
464-
465- clearTimeout ( bc . _freshenTimeout ) ;
466- bc . _freshenTimeout = setTimeout ( bc . freshenRandomPackage , delay ) ;
467- bc . _freshenTimeout . unref ( ) ;
468- } ;
469-
470344 /**
471345 * Given a list of acceptable formats, get the sorted list of of formats.
472346 * Actually used (as per node _webi/lint-builds.js):
@@ -669,29 +543,22 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
669543 return null ;
670544 }
671545
672- for ( let _triplet of triplets ) {
673- let targetReleases = projInfo . releasesByTriplet [ _triplet ] ;
674- if ( ! targetReleases ) {
675- continue ;
676- }
546+ // Iterate versions newest-first, then try each triplet for that
547+ // version. This ensures we get the latest version even if the
548+ // preferred triplet only has old releases (e.g. serviceman's
549+ // linux-x86_64-none triplet has only v0.8.0, but
550+ // posix_2017-ANYARCH-none has v1.0.1 — version-first finds v1.0.1
551+ // via the posix waterfall step).
552+ for ( let lexver of projInfo . lexvers ) {
553+ let ver = projInfo . lexversMap [ lexver ] || lexver ;
554+
555+ for ( let _triplet of triplets ) {
556+ let targetReleases = projInfo . releasesByTriplet [ _triplet ] ;
557+ if ( ! targetReleases ) {
558+ continue ;
559+ }
677560
678- let versions = Object . keys ( targetReleases ) ;
679- //console.log('dbg: targetRelease versions', versions);
680- let lexvers = [ ] ;
681- for ( let version of versions ) {
682- let lexPrefix = Lexver . parseVersion ( version ) ;
683- lexvers . push ( lexPrefix ) ;
684- }
685- lexvers . sort ( ) ;
686- lexvers . reverse ( ) ;
687- // TODO get the other matchInfo props
688-
689- // Make sure that these releases are the expected version
690- // (ex: jq1.7 => darwin-arm64-libc, jq1.6 => darwin-x86_64-libc)
691- for ( let matchver of lexvers ) {
692- let ver = projInfo . lexversMap [ matchver ] || matchver ;
693561 let packages = targetReleases [ ver ] ;
694- //console.log('dbg: packages', packages);
695562 if ( ! packages ) {
696563 continue ;
697564 }
@@ -789,9 +656,22 @@ BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
789656
790657BuildsCacher . _classify = function ( bc , projInfo , build ) {
791658 /* jshint maxcomplexity: 25 */
792- let maybeInstallable = Triplet . maybeInstallable ( projInfo , build ) ;
793- if ( ! maybeInstallable ) {
794- return null ;
659+ // The cache has already filtered to installable entries and tagged
660+ // each with os/arch/libc/ext. Re-running maybeInstallable here would
661+ // wrongly reject entries whose `name` is just a version tag:
662+ // - git source: `serviceman-v1.0.1` ends with `.1`
663+ // - github source archive: `v1.0.1.zip` from
664+ // `/legacy.zip/refs/tags/v1.0.1`
665+ // Both look like the man-page false positive that #1074 partially
666+ // addressed. Trust the cache when it carries full classification;
667+ // only run the filename heuristics for legacy/incomplete entries.
668+ let cacheClassified =
669+ build . os && build . arch && build . libc && build . ext ;
670+ if ( ! cacheClassified ) {
671+ let maybeInstallable = Triplet . maybeInstallable ( projInfo , build ) ;
672+ if ( ! maybeInstallable ) {
673+ return null ;
674+ }
795675 }
796676
797677 if ( LEGACY_OS_MAP [ build . os ] ) {
@@ -855,16 +735,30 @@ BuildsCacher._classify = function (bc, projInfo, build) {
855735 bc . unknownTerms [ term ] = true ;
856736 }
857737
858- // {NAME}.windows.x86_64v2.musl.exe
859- // windows-x86_64_v2-musl
738+ // webicached has already classified each cache entry with os/arch/
739+ // libc/ext. The filename re-parser (termsToTarget) is redundant for
740+ // those entries, and runs into spurious conflicts: e.g. a '.git' URL
741+ // makes the parser derive os=ANYOS, but the cache says
742+ // os='posix_2017', and the mismatch check throws. Trust the cache
743+ // when it carries full classification; only re-parse when fields
744+ // are missing (legacy installer entries without metadata).
860745 target = { triplet : '' } ;
861- try {
862- void Triplet . termsToTarget ( target , projInfo , build , terms ) ;
863- } catch ( e ) {
864- console . error ( `PACKAGE FORMAT CHANGE for '${ projInfo . name } ':` ) ;
865- console . error ( e . message ) ;
866- console . error ( build ) ;
867- return null ;
746+ if ( cacheClassified ) {
747+ target . os = build . os ;
748+ target . arch = build . arch ;
749+ target . libc = build . libc ;
750+ target . vendor = build . vendor || 'unknown' ;
751+ target . android = false ;
752+ target . unknownTerms = [ ] ;
753+ } else {
754+ try {
755+ void Triplet . termsToTarget ( target , projInfo , build , terms ) ;
756+ } catch ( e ) {
757+ console . error ( `PACKAGE FORMAT CHANGE for '${ projInfo . name } ':` ) ;
758+ console . error ( e . message ) ;
759+ console . error ( build ) ;
760+ return null ;
761+ }
868762 }
869763
870764 target . triplet = `${ target . arch } -${ target . vendor } -${ target . os } -${ target . libc } ` ;
0 commit comments