Diff
Not logged in

Differences From Artifact [2ca5ec045e]:

To Artifact [3ae0666b79]:


172
173
174
175
176
177
178

179
180
181













182
183
184
185
186
187
188


189
190
191
192
193
194

195
196

197
198
199
200
201
202
203
204
205
206




207


208
209
210
211





























































212
213

214
215
216
217
218
219
220
172
173
174
175
176
177
178
179



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197


198
199






200
201

202
203
204
205
206
207
208
209
210
211
212
213
214
215
216

217
218
219
220


221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282

283
284
285
286
287
288
289
290







+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+





-
-
+
+
-
-
-
-
-
-
+

-
+










+
+
+
+
-
+
+


-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
+








  #
  # NOTE: This procedure returns the root directory where any packages that
  #       are downloaded should be saved to permanent storage for subsequent
  #       use.  There are no arguments.
  #
  proc getPersistentRootDirectory {} {
    global env
    #
    # NOTE: Return a directory parallel to the one containing the library
    #       directory.

    #
    # NOTE: Allow the persistent root directory to be overridden via the
    #       environment.  Typically, this customization will only be needed
    #       if multiple instances of Tcl need to share packages.
    #
    if {[info exists env(PKGD_ROOT)]} then {
      return $env(PKGD_ROOT)
    }

    #
    # NOTE: Fallback to returning a directory parallel to the one containing
    #       the library directory.
    #
    return [file join [file dirname [info library]] pkgd]
  }

  #
  # NOTE: This procedure, which is only used with native Tcl, generates a
  #       package index file (i.e. "pkgIndex.tcl") suitable for use with
  # NOTE: This procedure checks the configured persistent root directory for
  #       downloaded packages.  If any checks fail, a script error is raised.
  #       native Tcl 8.4 (or higher).  It will recursively [source] other
  #       native Tcl package index files that are within the configured
  #       persistent root directory, thereby causing all packages located
  #       within it to become available.  Since Eagle (by default) already
  #       performs a recursive search for its package index files, this
  #       procedure is not necessary for Eagle packages.
  #       There are no arguments.  The return value is undefined.
  #
  proc maybeCreateMasterTclPackageIndex {} {
  proc verifyPersistentRootDirectory {} {
    variable persistentRootDirectory

    if {![info exists persistentRootDirectory]} then {
      error "persistent root directory not set"
    }

    if {[string length $persistentRootDirectory] == 0} then {
      error "persistent root directory is invalid"
    }

    #
    # NOTE: Either the persistent root directory must already exist -OR- we
    #       must be able to create it.
    #
    if {![file isdirectory $persistentRootDirectory]} then {
    if {![file isdirectory $persistentRootDirectory] && \
        [catch {file mkdir $persistentRootDirectory}]} then {
      error "persistent root directory does not exist"
    }

    set fileName [file join $persistentRootDirectory pkgIndex.tcl]
  }

  #
  # NOTE: This procedure returns the name of the package index file for the
  #       language specified by the language argument.  An empty string will
  #       be returned if the language is unsupported or unrecognized.
  #
  proc getPackageIndexFileName { language } {
    if {[string length $language] == 0 || $language eq "eagle"} then {
      return pkgIndex.eagle
    } elseif {$language eq "tcl"} then {
      return pkgIndex.tcl
    } else {
      return ""
    }
  }

  #
  # NOTE: This procedure returns non-zero if the specified file appears
  #       to contain a master package index for native Tcl.  The fileName
  #       argument is the name of the file to check.
  #
  proc isMasterTclPackageIndex { fileName } {
    #
    # NOTE: Read all data from the specified file.  This should always be
    #       a relatively small file with a constant size.
    #
    set data [readFile $fileName]

    #
    # NOTE: Check the data read from the file for the magic string that
    #       we use to indicate a "master package index" file.
    #
    if {[string first <MASTER_PACKAGE_INDEX> $data] == -1} then {
      return false
    }

    #
    # TODO: Make this procedure smarter?
    #
    return true
  }

  #
  # NOTE: This procedure, which is only used for native Tcl, generates a
  #       package index file (i.e. "pkgIndex.tcl") suitable for use with
  #       native Tcl 8.4 (or higher).  It will recursively [source] other
  #       native Tcl package index files that are within the configured
  #       persistent root directory, thereby causing all packages located
  #       within it to become available.  Since Eagle (by default) already
  #       performs a recursive search for its package index files, this
  #       procedure is not necessary for Eagle packages.  This procedure
  #       will return non-zero if productive work was done.
  #
  proc maybeModifyMasterTclPackageIndex {} {
    variable persistentRootDirectory

    verifyPersistentRootDirectory
    set persistentDirectory $persistentRootDirectory

    set fileName [file join $persistentDirectory pkgIndex.tcl]

    if {[file exists $fileName]} then {
    if {[file exists $fileName] && ![isMasterPackageIndex $fileName]} then {
      return false
    }

    writeFile $fileName [string trim [string map [list \r\n \n] {
###############################################################################
#
# pkgIndex.tcl --
248
249
250
251
252
253
254

255


256
257
258
259
260
261
262
263
264

265
266
267
268
269
270
271
272
273


274
275
276
277
278
279
280
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336

337
338
339
340
341
342
343
344
345

346
347
348
349
350
351
352
353
354







+

+
+








-
+








-
+
+







  }

  eval lappend pkgd(dirs) \
      [glob -nocomplain -types {d} [file join $pkgd(dir) *]]
}

set dir $pkgd(savedDir); unset -nocomplain pkgd
## <MASTER_PACKAGE_INDEX> ##
    }]]

    return true
  }

  #
  # NOTE: This procedure returns non-zero if the specified file seems to be
  #       an OpenPGP signature file.  The fileName argument is the name of
  #       the file to check, which may or may not exist.  The nameOnly
  #       argument should be non-zero to ignore the contents of the file.
  #
  proc isPgpSignatureFileName { fileName nameOnly } {
  proc isOpenPgpSignatureFileName { fileName nameOnly } {
    if {[string length $fileName] == 0} then {
      return false
    }

    set extension [file extension $fileName]

    if {$extension eq ".asc"} then {
      if {!$nameOnly && [file exists $fileName]} then {
        return [::PackageRepository::isPgpSignature [readFile $fileName]]
        return [::PackageRepository::isOpenPgpSignature \
            [readFile $fileName]]
      } else {
        return true
      }
    } else {
      return false
    }
  }
291
292
293
294
295
296
297
298


299
300
301
302
303
304
305














































306
307
308
309
310
311
312
365
366
367
368
369
370
371

372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433







-
+
+







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







      return false
    }

    set extension [file extension $fileName]

    if {$extension eq ".harpy"} then {
      if {!$nameOnly && [file exists $fileName]} then {
        return [::PackageRepository::isHarpyCertificate [readFile $fileName]]
        return [::PackageRepository::isHarpyCertificate \
            [readFile $fileName]]
      } else {
        return true
      }
    } else {
      return false
    }
  }

  #
  # NOTE: This procedure returns the auto-path for the language specified by
  #       the language argument.  An empty list is returned if the auto-path
  #       does not exist in the target language.  This procedure may raise
  #       script errors.
  #
  proc getAutoPath { language } {
    if {[string length $language] == 0 || $language eq "eagle"} then {
      if {[isEagle]} then {
        if {![info exists ::auto_path]} then {
          return [list]
        }

        return $::auto_path
      } else {
        ::PackageRepository::eagleMustBeReady

        eagle {
          if {![info exists ::auto_path]} then {
            return [list]
          }

          return $::auto_path
        }
      }
    } elseif {$language eq "tcl"} then {
      if {[isEagle]} then {
        tcl eval [tcl master] {
          if {![info exists ::auto_path]} then {
            return [list]
          }

          return $::auto_path
        }
      } else {
        if {![info exists ::auto_path]} then {
          return [list]
        }

        return $::auto_path
      }
    } else {
      error "unsupported language, no idea how to query auto-path"
    }
  }

  #
  # NOTE: This procedure adds a directory to the auto-path of the specified
  #       language (i.e. native Tcl or Eagle).  The directory will not be
  #       added if it is already present.  The language argument must be the
  #       literal string "eagle" or the literal string "tcl".  The directory
  #       argument is the fully qualified path for the directory to add to
347
348
349
350
351
352
353












































































354
355
356
357
358
359
360
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







          lappend ::auto_path $directory
        }
      }
    } else {
      error "unsupported language, no idea how to modify auto-path"
    }
  }

  #
  # NOTE: This procedure adds a directory to the auto-path of the specified
  #       language (i.e. native Tcl or Eagle).  The directory will not be
  #       added if it is already present.  The language argument must be the
  #       literal string "eagle" or the literal string "tcl".  The directory
  #       argument is the fully qualified path for the directory to add to
  #       the auto-path.  The directory will not be added if it falls under
  #       a directory already in the auto-path.
  #
  proc maybeAddToAutoPath { language directory } {
    #
    # NOTE: Verify that the directory to be added is valid and exists.  If
    #       not, do nothing.
    #
    if {[string length $directory] == 0 || \
        ![file isdirectory $directory]} then {
      return false
    }

    #
    # NOTE: Normalize the specified directory.  This is necessary so that
    #       we can compare apples-to-apples within the auto-path.
    #
    set directory [file normalize $directory]
    set directoryLength [string length $directory]

    #
    # NOTE: Query the auto-path for the target language.
    #
    set autoPath [getAutoPath $language]

    #
    # NOTE: Check each directory in the auto-path to see if the specified
    #       directory is already underneath it.
    #
    foreach autoDirectory $autoPath {
      #
      # NOTE: Normalize the auto-path directory.  This is necessary so
      #       that we can compare apples-to-apples with the specified
      #       directory.
      #
      set autoDirectory [file normalize $autoDirectory]
      set autoDirectoryLength [string length $autoDirectory]

      #
      # NOTE: Prefix match is impossible if the length of the specified
      #       directory is less than the length of this directory in the
      #       auto-path.
      #
      if {$directoryLength < $autoDirectoryLength} then {
        continue
      }

      #
      # NOTE: If the initial portion of the specified directory is the
      #       same as this directory in the auto-path, it must reside
      #       underneath it.  In that case, there is no need to modify
      #       the auto-path, bail out now.
      #
      set last [expr {$autoDirectoryLength - 1}]

      if {[string range $directory 0 $last] eq $autoDirectory} then {
        return false
      }
    }

    #
    # NOTE: At this point, it is pretty safe to assume that the specified
    #       directory is not in the auto-path, nor underneath a directory
    #       within the auto-path.
    #
    addToAutoPath $language $directory

    return true
  }

  #
  # NOTE: This procedure verifies the combination of language and version
  #       specified by the caller.  The language argument must be one of the
  #       literal strings "eagle", "tcl", or "client".  The version argument
  #       must be one of the literal strings "8.4", "8.5", or "8.6" when the
  #       language is "tcl" -OR- the literal string "1.0" when the language
591
592
593
594
595
596
597

598
599
600
601
602
603
604
605
606
607
608

609
610
611
612
613
614
615
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814







+











+







  #       directory on the package file server and may be an empty string.  The
  #       usePgp argument should be non-zero when an OpenPGP signature file
  #       needs to be downloaded and verified for the downloaded file.
  #
  # <public>
  proc checkForHigherVersion { language version packageName usePgp } {
    variable clientDirectory
    variable persistentRootDirectory
    variable temporaryRootDirectory

    verifyLanguageAndVersion $language $version isClient

    set temporaryDirectory [file join \
        $temporaryRootDirectory [appendArgs \
        pkgd_ver_ [::PackageRepository::getUniqueSuffix]]]

    if {$isClient} then {
      set persistentDirectory $clientDirectory
    } else {
      verifyPersistentRootDirectory
      set persistentDirectory $persistentRootDirectory
    }

    set fileName [file join $packageName VERSION]
    set downloadFileName [file join $temporaryDirectory $fileName]

    file mkdir [file dirname $downloadFileName]
669
670
671
672
673
674
675
676

677
678
679
680
681

682
683

684
685
686
687
688
689
690
868
869
870
871
872
873
874

875
876
877
878
879

880
881

882
883
884
885
886
887
888
889







-
+




-
+

-
+







    #
    writeFile $localFileName [getPackageFile $uri]

    #
    # NOTE: Is use of OpenPGP for signature verification enabled?  Also,
    #       did we just download an OpenPGP signature file?
    #
    if {$usePgp && [isPgpSignatureFileName $localFileName true]} then {
    if {$usePgp && [isOpenPgpSignatureFileName $localFileName true]} then {
      #
      # NOTE: Attempt to verify the OpenPGP signature.  If this fails,
      #       an error is raised.
      #
      if {![::PackageRepository::verifyPgpSignature $localFileName]} then {
      if {![::PackageRepository::verifyOpenPgpSignature $localFileName]} then {
        error [appendArgs \
            "bad PGP signature \"" $localFileName \"]
            "bad OpenPGP signature \"" $localFileName \"]
      }
    }
  }

  #
  # NOTE: This procedure attempts to download a list of files, optionally
  #       persistening them for subsequent uses by the target language.
716
717
718
719
720
721
722

723
724
725
726
727
728
729
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929







+







    set temporaryDirectory [file join \
        $temporaryRootDirectory [appendArgs \
        pkgd_lib_ [::PackageRepository::getUniqueSuffix]]]

    if {$isClient} then {
      set persistentDirectory $clientDirectory
    } else {
      verifyPersistentRootDirectory
      set persistentDirectory $persistentRootDirectory
    }

    set downloadedFileNames [list]

    foreach fileName $fileNames {
      if {[string length $fileName] == 0 || \
758
759
760
761
762
763
764
765

766
767
768
769
770
771
772
958
959
960
961
962
963
964

965
966
967
968
969
970
971
972







-
+








      file mkdir [file dirname $downloadFileName]
      downloadOneFile $language $version $fileName $downloadFileName $usePgp

      lappend downloadedFileNames [list \
          $fileNameOnly $directory(temporary) $directory(persistent)]

      if {$usePgp && ![isPgpSignatureFileName $downloadFileName true]} then {
      if {$usePgp && ![isOpenPgpSignatureFileName $downloadFileName true]} then {
        downloadOneFile $language $version [appendArgs $fileName .asc] \
            [appendArgs $downloadFileName .asc] $usePgp

        lappend downloadedFileNames [list \
            [appendArgs $fileNameOnly .asc] $directory(temporary) \
            $directory(persistent)]
      }
780
781
782
783
784
785
786




787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802






























803
804
805






















806
807
808
809
810







811
812
813
814
815
816
817
818
819




820
821
822
823
824
825
826
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061





1062
1063
1064
1065
1066
1067
1068






1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082







+
+
+
+
















+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-



+
+
+
+







      if {$persistent || $viaInstall} then {
        set fileNameOnly [lindex $downloadedFileName 0]
        set directory(persistent) [lindex $downloadedFileName 2]

        file mkdir $directory(persistent)
        set command [list file copy]

        #
        # NOTE: When updating the package repository client files, always
        #       use the -force option to overwrite existing files.
        #
        if {$isClient} then {
          lappend command -force
        }

        lappend command --
        lappend command [file join $directory(temporary) $fileNameOnly]
        lappend command [file join $directory(persistent) $fileNameOnly]

        eval $command

        lappend downloadDirectories $directory(persistent)
      } else {
        lappend downloadDirectories $directory(temporary)
      }
    }

    #
    # NOTE: Does the package need to be persisted locally?  This can be set
    #       via the direct caller or via the installer tool.
    #
    set addPersistentDirectoryToAutoPath false

    if {$persistent || $viaInstall} then {
      #
      # NOTE: In Eagle, a slightly different command is required to delete
      #       an entire directory tree.
      #
      if {[isEagle]} then {
        file delete -recursive -- $temporaryDirectory
      } else {
        file delete -force -- $temporaryDirectory
      }

      #
      # NOTE: When dealing with packages for native Tcl, modify the master
      #       package index.
      #
      if {$language eq "tcl"} then {
        set addPersistentDirectoryToAutoPath [maybeModifyMasterTclPackageIndex]
      }
    }

    #
    # NOTE: Sort the list of directories that downloaded files were written
    #       to, removing any duplicates in the process.
    #
    set downloadDirectories [lsort -unique $downloadDirectories]

    if {$useAutoPath} then {
      #
      # NOTE: The auto-path, for whatever language this package belongs to,
      #       needs to be modified.
      #
      if {$addPersistentDirectoryToAutoPath} then {
        #
        # NOTE: The downloaded package was persisted -AND- will be handled
        #       by the master package index; therefore, just make sure the
        #       package persistence root directory is in the auto-path and
        #       then return only that directory in the result.
        #
        maybeAddToAutoPath $language $persistentDirectory
        set downloadDirectories [list $persistentDirectory]
      } else {
        #
        # NOTE: Check each unique download directory for a package index
        #       file.  If a directory has a package index for the target
        #       language, add to the auto-path for the target language.
        #
        set packageIndexFileName [getPackageIndexFileName $language]

        if {[string length $packageIndexFileName] > 0} then {
      foreach downloadDirectory $downloadDirectories {
        addToAutoPath $language $downloadDirectory
      }
    }

          foreach downloadDirectory $downloadDirectories {
            if {[file exists [file join \
                $downloadDirectory $packageIndexFileName]]} then {
              addToAutoPath $language $downloadDirectory
            }
          }
        }
    if {$persistent || $viaInstall} then {
      if {[isEagle]} then {
        file delete -recursive -- $temporaryDirectory
      } else {
        file delete -force -- $temporaryDirectory
        maybeCreateMasterTclPackageIndex
      }
    }

    #
    # NOTE: Always return the list of directories that were actually added
    #       to the auto-path, if any.
    #
    return $downloadDirectories
  }

  #
  # NOTE: This package requires that support for namespaces, which is an
  #       optional feature of Eagle, must be enabled.
  #
840
841
842
843
844
845
846










847
848
849
850
851
852
853
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119







+
+
+
+
+
+
+
+
+
+







  ::PackageRepository::maybeReadSettingsFile [info script]

  #
  # NOTE: Setup the variables, within this namespace, used by this script.
  #
  setupDownloadVars [info script]

  #
  # NOTE: If necessary, add the package persistence root directory to the
  #       auto-path for the current language.  This will only be done if
  #       it falls outside of the existing auto-path.
  #
  variable persistentRootDirectory

  maybeAddToAutoPath [expr {[isEagle] ? "eagle" : "tcl"}] \
      $persistentRootDirectory

  #
  # NOTE: Provide the package to the interpreter.
  #
  package provide Eagle.Package.Downloader \
    [expr {[isEagle] ? [info engine PatchLevel] : "1.0"}]
}