@@ -208,7 +208,7 @@ public static function downloadFile(string $name, string $url, string $filename,
208208 if ($ download_as === SPC_DOWNLOAD_PRE_BUILT ) {
209209 $ name = self ::getPreBuiltLockName ($ name );
210210 }
211- self ::lockSource ($ name , ['source_type ' => ' archive ' , 'filename ' => $ filename , 'move_path ' => $ move_path , 'lock_as ' => $ download_as ]);
211+ self ::lockSource ($ name , ['source_type ' => SPC_SOURCE_ARCHIVE , 'filename ' => $ filename , 'move_path ' => $ move_path , 'lock_as ' => $ download_as ]);
212212 }
213213
214214 /**
@@ -231,6 +231,9 @@ public static function lockSource(string $name, array $data): void
231231 } else {
232232 $ lock = json_decode (FileSystem::readFile (DOWNLOAD_PATH . '/.lock.json ' ), true ) ?? [];
233233 }
234+ // calculate hash
235+ $ hash = self ::getLockSourceHash ($ data );
236+ $ data ['hash ' ] = $ hash ;
234237 $ lock [$ name ] = $ data ;
235238 FileSystem::writeFile (DOWNLOAD_PATH . '/.lock.json ' , json_encode ($ lock , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ));
236239 }
@@ -278,7 +281,7 @@ public static function downloadGit(string $name, string $url, string $branch, ?s
278281 }
279282 // Lock
280283 logger ()->debug ("Locking git source {$ name }" );
281- self ::lockSource ($ name , ['source_type ' => ' dir ' , 'dirname ' => $ name , 'move_path ' => $ move_path , 'lock_as ' => $ lock_as ]);
284+ self ::lockSource ($ name , ['source_type ' => SPC_SOURCE_GIT , 'dirname ' => $ name , 'move_path ' => $ move_path , 'lock_as ' => $ lock_as ]);
282285
283286 /*
284287 // 复制目录过去
@@ -371,6 +374,16 @@ public static function downloadPackage(string $name, ?array $pkg = null, bool $f
371374 SPC_DOWNLOAD_PRE_BUILT
372375 );
373376 break ;
377+ case 'local ' :
378+ // Local directory, do nothing, just lock it
379+ logger ()->debug ("Locking local source {$ name }" );
380+ self ::lockSource ($ name , [
381+ 'source_type ' => SPC_SOURCE_LOCAL ,
382+ 'dirname ' => $ pkg ['dirname ' ],
383+ 'move_path ' => $ pkg ['extract ' ] ?? null ,
384+ 'lock_as ' => SPC_DOWNLOAD_PACKAGE ,
385+ ]);
386+ break ;
374387 case 'custom ' : // Custom download method, like API-based download or other
375388 $ classes = FileSystem::getClassesPsr4 (ROOT_DIR . '/src/SPC/store/source ' , 'SPC\store\source ' );
376389 foreach ($ classes as $ class ) {
@@ -477,6 +490,16 @@ public static function downloadSource(string $name, ?array $source = null, bool
477490 $ download_as
478491 );
479492 break ;
493+ case 'local ' :
494+ // Local directory, do nothing, just lock it
495+ logger ()->debug ("Locking local source {$ name }" );
496+ self ::lockSource ($ name , [
497+ 'source_type ' => SPC_SOURCE_LOCAL ,
498+ 'dirname ' => $ source ['dirname ' ],
499+ 'move_path ' => $ source ['extract ' ] ?? null ,
500+ 'lock_as ' => $ download_as ,
501+ ]);
502+ break ;
480503 case 'custom ' : // Custom download method, like API-based download or other
481504 if (isset ($ source ['func ' ]) && is_callable ($ source ['func ' ])) {
482505 $ source ['name ' ] = $ name ;
@@ -594,6 +617,43 @@ public static function getPreBuiltLockName(string $source): string
594617 return "{$ source }- " . PHP_OS_FAMILY . '- ' . getenv ('GNU_ARCH ' ) . '- ' . (getenv ('SPC_LIBC ' ) ?: 'default ' ) . '- ' . (SystemUtil::getLibcVersionIfExists () ?? 'default ' );
595618 }
596619
620+ /**
621+ * Get the hash of the lock source based on the lock options.
622+ *
623+ * @param array $lock_options Lock options
624+ * @return string Hash of the lock source
625+ * @throws RuntimeException
626+ */
627+ public static function getLockSourceHash (array $ lock_options ): string
628+ {
629+ $ result = match ($ lock_options ['source_type ' ]) {
630+ SPC_SOURCE_ARCHIVE => sha1_file (DOWNLOAD_PATH . '/ ' . $ lock_options ['filename ' ]),
631+ SPC_SOURCE_GIT => exec ('cd ' . escapeshellarg (DOWNLOAD_PATH . '/ ' . $ lock_options ['dirname ' ]) . ' && ' . SPC_GIT_EXEC . ' rev-parse HEAD ' ),
632+ SPC_SOURCE_LOCAL => 'LOCAL HASH IS ALWAYS DIFFERENT ' ,
633+ default => filter_var (getenv ('SPC_IGNORE_BAD_HASH ' ), FILTER_VALIDATE_BOOLEAN ) ? '' : throw new RuntimeException ("Unknown source type: {$ lock_options ['source_type ' ]}" ),
634+ };
635+ if ($ result === false && !filter_var (getenv ('SPC_IGNORE_BAD_HASH ' ), FILTER_VALIDATE_BOOLEAN )) {
636+ throw new RuntimeException ("Failed to get hash for source: {$ lock_options ['source_type ' ]}" );
637+ }
638+ return $ result ?: '' ;
639+ }
640+
641+ /**
642+ * @param array $lock_options Lock options
643+ * @param string $destination Target directory
644+ * @throws FileSystemException
645+ * @throws RuntimeException
646+ */
647+ public static function putLockSourceHash (array $ lock_options , string $ destination ): void
648+ {
649+ $ hash = self ::getLockSourceHash ($ lock_options );
650+ if ($ lock_options ['source_type ' ] === SPC_SOURCE_LOCAL ) {
651+ logger ()->debug ("Source [ {$ lock_options ['dirname ' ]}] is local, no hash will be written. " );
652+ return ;
653+ }
654+ FileSystem::writeFile ("{$ destination }/.spc-hash " , $ hash );
655+ }
656+
597657 /**
598658 * Register CTRL+C event for different OS.
599659 *
@@ -640,8 +700,8 @@ private static function isAlreadyDownloaded(string $name, bool $force, int $down
640700 // If lock file exists, skip downloading for source mode
641701 if (!$ force && $ download_as === SPC_DOWNLOAD_SOURCE && isset ($ lock [$ name ])) {
642702 if (
643- $ lock [$ name ]['source_type ' ] === ' archive ' && file_exists (DOWNLOAD_PATH . '/ ' . $ lock [$ name ]['filename ' ]) ||
644- $ lock [$ name ]['source_type ' ] === ' dir ' && is_dir (DOWNLOAD_PATH . '/ ' . $ lock [$ name ]['dirname ' ])
703+ $ lock [$ name ]['source_type ' ] === SPC_SOURCE_ARCHIVE && file_exists (DOWNLOAD_PATH . '/ ' . $ lock [$ name ]['filename ' ]) ||
704+ $ lock [$ name ]['source_type ' ] === SPC_SOURCE_GIT && is_dir (DOWNLOAD_PATH . '/ ' . $ lock [$ name ]['dirname ' ])
645705 ) {
646706 logger ()->notice ("Source [ {$ name }] already downloaded: " . ($ lock [$ name ]['filename ' ] ?? $ lock [$ name ]['dirname ' ]));
647707 return true ;
@@ -652,8 +712,8 @@ private static function isAlreadyDownloaded(string $name, bool $force, int $down
652712 if (!$ force && $ download_as === SPC_DOWNLOAD_PRE_BUILT && isset ($ lock [$ lock_name = self ::getPreBuiltLockName ($ name )])) {
653713 // lock name with env
654714 if (
655- $ lock [$ lock_name ]['source_type ' ] === ' archive ' && file_exists (DOWNLOAD_PATH . '/ ' . $ lock [$ lock_name ]['filename ' ]) ||
656- $ lock [$ lock_name ]['source_type ' ] === ' dir ' && is_dir (DOWNLOAD_PATH . '/ ' . $ lock [$ lock_name ]['dirname ' ])
715+ $ lock [$ lock_name ]['source_type ' ] === SPC_SOURCE_ARCHIVE && file_exists (DOWNLOAD_PATH . '/ ' . $ lock [$ lock_name ]['filename ' ]) ||
716+ $ lock [$ lock_name ]['source_type ' ] === SPC_SOURCE_GIT && is_dir (DOWNLOAD_PATH . '/ ' . $ lock [$ lock_name ]['dirname ' ])
657717 ) {
658718 logger ()->notice ("Pre-built content [ {$ name }] already downloaded: " . ($ lock [$ lock_name ]['filename ' ] ?? $ lock [$ lock_name ]['dirname ' ]));
659719 return true ;
0 commit comments