1717package nextflow.module
1818
1919import groovy.transform.CompileStatic
20- import groovy.util.logging.Slf4j
20+ import io.seqera.npr.client.ModuleChecksum as SharedChecksum
2121
22- import java.nio.file.Files
2322import java.nio.file.Path
24- import java.security.MessageDigest
25-
26- import static nextflow.module.ModuleInfo.MODULE_INFO_FILE
2723
2824/**
29- * Utility class for computing SHA-256 checksums of module directories
25+ * Nextflow-specific checksum utility that delegates to the shared
26+ * {@link io.seqera.npr.client.ModuleChecksum} implementation and adds
27+ * convenience methods for .module-info persistence.
3028 *
3129 * @author Jorge Ejarque <jorge.ejarque@seqera.io>
3230 */
33- @Slf4j
3431@CompileStatic
3532class ModuleChecksum {
3633
37- public static final String CHECKSUM_ALGORITHM = " SHA-256 "
34+ public static final String CHECKSUM_ALGORITHM = SharedChecksum . ALGORITHM
3835
3936 /**
4037 * Compute the SHA-256 checksum of a module directory
41- *
42- * @param moduleDir The module directory path
43- * @return The hex-encoded SHA-256 checksum
4438 */
4539 static String compute (Path moduleDir ) {
46- if ( ! Files . exists(moduleDir) || ! Files . isDirectory(moduleDir) ) {
47- throw new IllegalArgumentException (" Module directory does not exist or is not a directory: ${ moduleDir} " )
48- }
49-
50- try {
51- def digest = MessageDigest . getInstance(CHECKSUM_ALGORITHM )
52-
53- // Collect all files in sorted order for consistent checksums
54- List<Path > files = []
55- try ( final walkStream = Files . walk(moduleDir) ) {
56- walkStream
57- .filter { Path path -> Files . isRegularFile(path) }
58- .filter { Path path -> ! path. fileName. toString(). equals(MODULE_INFO_FILE ) }
59- .sorted()
60- .each { Path path -> files. add(path) }
61- }
62-
63- // Compute checksum over all file contents
64- byte [] buf = new byte [8192 ]
65- for ( Path file : files ) {
66- // Include relative path in checksum for directory structure integrity
67- def relativePath = moduleDir. relativize(file). toString()
68- digest. update(relativePath. bytes)
69-
70- // Include file contents via streaming to avoid loading large files into memory
71- Files . newInputStream(file). withCloseable { is ->
72- int n
73- while ( (n = is. read(buf)) != -1 ) {
74- digest. update(buf, 0 , n)
75- }
76- }
77- }
78-
79- def hashBytes = digest. digest()
80- return bytesToHex(hashBytes)
81- }
82- catch ( Exception e ) {
83- log. error(" Failed to compute checksum for module directory: ${ moduleDir} " , e)
84- throw new RuntimeException (" Failed to compute module checksum" , e)
85- }
40+ return SharedChecksum . computeDirectory(moduleDir)
8641 }
8742
8843 /**
89- * Save a checksum to the .module-info file in the module directory
90- *
91- * @param moduleDir The module directory path
92- * @param checksum The checksum to save
44+ * Save a checksum to the .module-info file
9345 */
9446 static void save (Path moduleDir , String checksum ) {
95- ModuleInfo . save(moduleDir,' checksum' , checksum)
47+ ModuleInfo . save(moduleDir, ' checksum' , checksum)
9648 }
9749
9850 /**
99- * Load a checksum from the .module-info file in the module directory
100- *
101- * @param moduleDir The module directory path
102- * @return The checksum, or null if file doesn't exist
51+ * Load a checksum from the .module-info file
10352 */
10453 static String load (Path moduleDir ) {
10554 ModuleInfo . load(moduleDir, ' checksum' )
10655 }
10756
10857 /**
10958 * Verify that a module directory matches the expected checksum
110- *
111- * @param moduleDir The module directory path
112- * @param expectedChecksum The expected checksum
113- * @return true if checksums match, false otherwise
11459 */
11560 static boolean verify (Path moduleDir , String expectedChecksum ) {
11661 def actualChecksum = compute(moduleDir)
@@ -119,46 +64,8 @@ class ModuleChecksum {
11964
12065 /**
12166 * Compute the checksum of a single file
122- *
123- * @param file The file path
124- * @param type checksum algorithm (sha-256 if not provided)
125- * @return The hex-encoded checksum
12667 */
12768 static String computeFile (Path file , String type = CHECKSUM_ALGORITHM ) {
128- if ( ! Files . exists(file) || ! Files . isRegularFile(file) ) {
129- throw new IllegalArgumentException (" File does not exist or is not a regular file: ${ file} " )
130- }
131-
132- try {
133- final digest = MessageDigest . getInstance(type)
134- final byte [] buf = new byte [8192 ]
135- Files . newInputStream(file). withCloseable { is ->
136- int n
137- while ( (n = is. read(buf)) != -1 ) {
138- digest. update(buf, 0 , n)
139- }
140- }
141- return bytesToHex(digest. digest())
142- }
143- catch ( Exception e ) {
144- log. error(" Failed to compute checksum for file: ${ file} " , e)
145- throw new RuntimeException (" Failed to compute file checksum" , e)
146- }
147- }
148-
149- /**
150- * Convert byte array to hex string
151- *
152- * @param bytes The byte array
153- * @return Hex-encoded string
154- */
155- private static String bytesToHex (byte [] bytes ) {
156- def hexChars = new char [bytes. length * 2 ]
157- for ( int i = 0 ; i < bytes. length; i++ ) {
158- int v = bytes[i] & 0xFF
159- hexChars[i * 2 ] = Character . forDigit(v >>> 4 , 16 )
160- hexChars[i * 2 + 1 ] = Character . forDigit(v & 0x0F , 16 )
161- }
162- return new String (hexChars)
69+ return SharedChecksum . computeFile(file, type)
16370 }
16471}
0 commit comments