diff --git a/mingw-w64-petsc/PKGBUILD b/mingw-w64-petsc/PKGBUILD index b9d5c11fc6..406ad4708f 100644 --- a/mingw-w64-petsc/PKGBUILD +++ b/mingw-w64-petsc/PKGBUILD @@ -7,7 +7,7 @@ pkgname=("${MINGW_PACKAGE_PREFIX}-${_realname}" "${MINGW_PACKAGE_PREFIX}-${_real # required at compile time by some packages (such as SLEPc). # Regular users should use the `petsc` package. pkgver=3.14.2 -pkgrel=1 +pkgrel=2 arch=('any') pkgdesc='Sparse iterative (non)linear solver package (mingw-w64)' depends=("${MINGW_PACKAGE_PREFIX}-gcc-libs" @@ -24,16 +24,24 @@ options=('strip' 'staticlibs') license=('2-clause BSD') url="https://www.mcs.anl.gov/petsc/" source=("http://ftp.mcs.anl.gov/pub/petsc/release-snapshots/${_realname}-lite-${pkgver}.tar.gz" + 'all.tcl' + 'tclbuildtest.tcl' + 'petsc.test' + 'petsc-mat.test' '0001-mpi-detection-override.patch') noextract=("${_realname}-lite-${pkgver}.tar.gz") sha256sums=('87a04fd05cac20a2ec47094b7d18b96e0651257d8c768ced2ef7db270ecfb9cb' + 'ec5072630e1c0309fe383669e9187790cd135a393c67bc4bc35cf60b0ba396ff' + '15c7af25b91406d5fe5f26cfe00963b6cfde1c3dd466eb25f1b6fae299934966' + '9662cc4104726c714f7961aba6f8c93bfb85f3d3f89b4908b0a55aedab41ca31' + 'fc95ca4ddc138d288f047835a68a84a11cb841c6cca146c6ef7d7e63e0836b1a' 'a28912b086807df7381abaec28353023a148f17a594abb6a1478f3b7b434373b') prepare() { mkdir -p $srcdir/build-${MINGW_CHOST} && cd $srcdir/build-${MINGW_CHOST} tar xzf $srcdir/${_realname}-lite-${pkgver}.tar.gz cd ${_realname}-${pkgver} - for p in ${source[*]:1}; do + for p in ${source[*]:5}; do patch -p1 -i "$srcdir/$p" done } @@ -130,7 +138,7 @@ eval "package_${MINGW_PACKAGE_PREFIX}-${_realname}-build() { _package_build; }" _package() { cd ${srcdir}/build-${MINGW_CHOST}/${_realname}-${pkgver} - mkdir -p ${pkgdir}${MINGW_PREFIX}/{bin,lib/pkgconfig,lib/${_realname},include/${_realname}} + mkdir -p ${pkgdir}${MINGW_PREFIX}/{bin,lib/pkgconfig,lib/${_realname},include/${_realname},share/test/petsc/{ksp,mat}} ( cd include cp *.h ${pkgdir}${MINGW_PREFIX}/include/${_realname} @@ -139,6 +147,18 @@ _package() { cd ${pkgdir}${MINGW_PREFIX}/include/${_realname} find . \( ! -name '*.h' -a -type f \) -delete ) + ( + cd ${srcdir} + cp {all,tclbuildtest}.tcl {petsc,petsc-mat}.test ${pkgdir}${MINGW_PREFIX}/share/test/petsc + ) + ( + cd src/ksp/ksp/tutorials + cp ex1.c ex2.c ex1f.F90 ex2f.F90 ${pkgdir}${MINGW_PREFIX}/share/test/petsc/ksp + ) + ( + cd src/mat/tutorials + cp ex15.c ex15f.F90 ${pkgdir}${MINGW_PREFIX}/share/test/petsc/mat + ) for build in ${builds}; do _petsc ${build} ( diff --git a/mingw-w64-petsc/all.tcl b/mingw-w64-petsc/all.tcl new file mode 100644 index 0000000000..b9ea41a400 --- /dev/null +++ b/mingw-w64-petsc/all.tcl @@ -0,0 +1,7 @@ +# Template {all.tcl} file which is to be put into each test module's directory. +# Either [source] the bundled {tclbuildtest.tcl} to force load the specific code or +# alter the $auto_path list to add location of {pkgIndex.tcl,tclbuildtest.tcl}. +# The same scheme is to be applied to all .test files. +source [file join [file dirname [file normalize [info script]]] tclbuildtest.tcl] +package require tclbuildtest +::tclbuildtest::suite {*}$::argv \ No newline at end of file diff --git a/mingw-w64-petsc/petsc-mat.test b/mingw-w64-petsc/petsc-mat.test new file mode 100644 index 0000000000..b5cc1948ec --- /dev/null +++ b/mingw-w64-petsc/petsc-mat.test @@ -0,0 +1,22 @@ +source [file join [file dirname [file normalize [info script]]] tclbuildtest.tcl] + +package require tclbuildtest + +# Tests of the Mat functionality +::tclbuildtest::sandbox { + foreach b {{} static} { + foreach p {single double} { + foreach s {real complex} { + foreach x {mpi} { + # Partitioner tests + test [list c $x $p $s $b] { + run [build [require petsc-[xyz]] mat/ex15.c] -mat_partitioning_type parmetis -N 100 + } + test [list fortran $x $p $s $b] { + run [build [require petsc-[xyz]] -ffree-line-length-256 mat/ex15f.F90] -mat_partitioning_type parmetis -N 100 + } + } + } + } + } +} \ No newline at end of file diff --git a/mingw-w64-petsc/petsc.test b/mingw-w64-petsc/petsc.test new file mode 100644 index 0000000000..6226c8cdc6 --- /dev/null +++ b/mingw-w64-petsc/petsc.test @@ -0,0 +1,27 @@ +source [file join [file dirname [file normalize [info script]]] tclbuildtest.tcl] + +package require tclbuildtest + +# Basic PETSc tests +::tclbuildtest::sandbox { + foreach b {{} static} { + foreach p {single double} { + foreach s {real complex} { + foreach x {{} openmp} { + test [list c $x $p $s $b] { + run [build [require petsc-[xyz]] ksp/ex1.c] + } + test [list fortran $x $p $s $b] { + run [build [require petsc-[xyz]] -ffree-line-length-256 ksp/ex1f.F90] + } + } + test [list c mpi $p $s $b] { + run [build [require petsc-[xyz]] ksp/ex2.c] + } + test [list fortran mpi $p $s $b] { + run [build [require petsc-[xyz]] ksp/ex2f.F90] + } + } + } + } +} \ No newline at end of file diff --git a/mingw-w64-petsc/tclbuildtest.tcl b/mingw-w64-petsc/tclbuildtest.tcl new file mode 100644 index 0000000000..ed801400f7 --- /dev/null +++ b/mingw-w64-petsc/tclbuildtest.tcl @@ -0,0 +1,751 @@ +# +# TclTest extenstion to test source code compilation & running +# +# https://github.com/okhlybov/tclbuildtest +# + +package provide tclbuildtest 0.1.0 + +package require Tcl 8.6 +package require tcltest 2.5.1 + +namespace eval ::tcltest {}; # Make pkg_mkIndex happy + +# https://github.com/tcl2020/named-parameters + +namespace eval ::np { + # + # proc_args_to_dict - given a proc name and declared + # proc arguments (variable names with optional + # default values and a -- and possible some more + # stuff, create and return a dict containing that + # info in a way that's convenient and quicker + # for us at runtime: + # * we store a list of positional parameters + # * we store a list of named parameters + # * we store a list of var-value defaults + # * we get a tricked-out error message in errmsg + # + ::proc proc_args_to_dict {name procArgs} { + set seenDashes 0 + dict set d defaults [list] + dict set d positional [list] + dict set d named [list] + set errmsg "wrong # args: should be \"$name " + + foreach arg $procArgs { + if {$arg eq "--"} { + set seenDashes 1 + append errmsg "?--? " + continue + } + + set var [lindex $arg 0] + dict lappend d [expr {$seenDashes ? "positional" : "named"}] $var + + if {[llength $arg] == 2} { + dict lappend d defaults $var [lindex $arg 1] + if {$seenDashes} { + append errmsg "?$var? " + } else { + append errmsg "?-$var val? " + } + } elseif {$var eq "args"} { + dict lappend d defaults $var [list] + append errmsg "?arg ...? " + } else { + if {$seenDashes} { + append errmsg "$var " + } else { + append errmsg "-$var val " + } + } + } + dict set d errmsg "[string range $errmsg 0 end-1]\"" + return $d + } + + # + # np_handler - look at an argument dict created by proc_args_to_dict + # and look at the real arguments to a function (args), and sort + # out the named and positional parameters to behave in the + # expected way. + # + ::proc np_handler {argd realArgs} { + set named [dict get $argd named] + set positional [dict get $argd positional] + + # process named parameters + while {[llength $realArgs] > 0} { + set arg [lindex $realArgs 0] + + # if arg is --, flip to positional + if {$arg eq "--"} { + set realArgs [lrange $realArgs 1 end] + break + } + + # if "var" doesn't start with a dash, flip to positional + if {[string index $arg 0] ne "-"} { + #puts "possible var '$arg' doesn't start with a dash, flip to positional" + break + } + + # if "var" isn't known to us as a named parameter, flip to positional + set var [string range $arg 1 end] + if {[lsearch $named $var] < 0} { + #puts "'var' '$arg' not recognized, flip to positional" + break + } + + # if there isn't at least one more element in the arg list, + # we are missing a value for one of our named parameters + if {[llength $realArgs] == 0} { + #puts "realArgs is empty but i expect something for $var" + error [dict get $argd errmsg] "" [list TCL WRONGARGS] + } + + # we're good, set the named parameter into the variable sets + #puts [list set vsets($var) [lindex $realArgs 1]] + + # but don't allow the same variable to be set twice + if {[info exists vsets($var)]} { + error [dict get $argd errmsg] "" [list TCL WRONGARGS] + } + + set vsets($var) [lindex $realArgs 1] + set realArgs [lrange $realArgs 2 end] + } + + # fill in defaults for all the vars with defaults that + # didn't get set to a value + foreach "var value" [dict get $argd defaults] { + if {![info exists vsets($var)]} { + set vsets($var) $value + } + } + + foreach var $positional { + if {$var eq "args"} { + set vsets($var) $realArgs + set realArgs [list] + break + } + + if {[llength $realArgs] > 0} { + set vsets($var) [lindex $realArgs 0] + set realArgs [lrange $realArgs 1 end] + } + + # no arguments left. if this var doesn't + # have a default value, it's a wrong args error + if {![info exists vsets($var)]} { + error [dict get $argd errmsg] "" [list TCL WRONGARGS] + } + } + + # make sure all the named parameters have been set, either + # by defaults or explicitly, any not set is an error + foreach var $named { + if {![info exists vsets($var)]} { + #puts "required named parameter '-$var' is not set" + error [dict get $argd errmsg] "" [list TCL WRONGARGS] + } + } + + # are there too many arguments? + if {[llength $realArgs] > 0} { + #puts "leftover arguments (too many) '$realArgs'" + error [dict get $argd errmsg] "" [list TCL WRONGARGS] + } + + # now iterate through the var-value pairs and set them into + # the caller's frame + foreach "var value" [array get vsets] { + #puts "set '$var' '$value'" + upvar $var myvar + set myvar $value + } + return + } + + # + # np::proc - same as proc except if -- is in the argv + # then it will generate a proc that has extra code + # at the beginning to wrangle the named parameters + # + proc proc {name argv body} { + # handle the case where there are no named parameters + if {[lsearch $argv --] < 0} { + uplevel [list ::proc $name $argv $body] + return + } + + if {[lsearch $argv --] == 0} { + return -code error "-- cannot be the first argument for named parameters. Use positional parameters." + } + + set d [proc_args_to_dict $name $argv] + set newbody "::proc $name args {\n" + append newbody " ::np::np_handler [list $d] \$args\n" + append newbody $body + append newbody "\n}" + #puts $newbody + uplevel $newbody + } +} + +namespace eval ::tclbuildtest { + + variable system-count 0 + variable build-count 0 + + # Standard predefined constraints + foreach ct { + c cxx fortran + single double + real complex + openmp thread hybrid mpi + static + debug + } {::tcltest::testConstraint $ct 1} + + # Return a value of the environment variable or {} if no such variable is set + proc env {var} { + try {return [set ::env($var)]} on error {} {return {}} + } + + # MpiExec detection + proc mpiexec {} { + variable mpiexec + try {set mpiexec} on error {} { + set mpiexec {} + foreach x [collect [env MPIEXEC] mpiexec mpirun] { + if {![catch {exec {*}$x}]} { + set mpiexec $x + break + } + } + } + if {$mpiexec == {}} {error {failed to detect MpiExec or equivalent}} + return $mpiexec + } + + # PkgConfig detection + proc pkg-config {} { + variable pc + try {set pc} on error {} { + set pc {} + foreach x [collect [env PKG_CONFIG] pkg-config pkgconf] { + if {![catch {exec {*}$x --version}]} { + set pc $x + break + } + } + } + if {$pc == {}} {error {failed to detect PkgConfig or equivalent}} + return $pc + } + + # C compiler detection + proc cc {} { + variable cc + try {set cc} on error {} { + set cc {} + foreach x [collect [env CC] gcc] { + if {![catch {exec {*}$x --version}]} { + set cc $x + break + } + } + } + if {$cc == {}} {error {failed to detect C compiler}} + return $cc + } + + # C++ compiler detection + proc cxx {} { + variable cxx + try {set cxx} on error {} { + set cxx {} + foreach x [collect [env CXX] g++] { + if {![catch {exec {*}$x --version}]} { + set cxx $x + break + } + } + } + if {$cxx == {}} {error {failed to detect C++ compiler}} + return $cxx + } + + # FORTRAN compiler detection + proc fc {} { + variable fc + try {set fc} on error {} { + set fc {} + foreach x [collect [env FC] gfortran] { + if {![catch {exec {*}$x --version}]} { + set fc $x + break + } + } + } + if {$fc == {}} {error {failed to detect FORTRAN compiler}} + return $fc + } + + # MPI C compiler detection + proc mpicc {} { + variable mpicc + try {set mpicc} on error {} { + set mpicc {} + foreach x [collect [env MPICC] mpicc] { + if {![catch {exec {*}$x --version}]} { + set mpicc $x + break + } + } + } + if {$mpicc == {}} {error {failed to detect MPI C compiler}} + return $mpicc + } + + # MPI C++ compiler detection + proc mpicxx {} { + variable mpicxx + try {set mpicxx} on error {} { + set mpicxx {} + foreach x [collect [env MPICXX] mpicxx mpic++] { + if {![catch {exec {*}$x --version}]} { + set mpicxx $x + break + } + } + } + if {$mpicxx == {}} {error {failed to detect MPI C++ compiler}} + return $mpicxx + } + + # MPI FORTRAN compiler detection + proc mpifc {} { + variable mpifc + try {set mpifc} on error {} { + set mpifc {} + foreach x [collect [env MPIFC] [env MPIFORT] mpifort mpif90 mpif77] { + if {![catch {exec {*}$x --version}]} { + set mpifc $x + break + } + } + } + if {$mpifc == {}} {error {failed to detect MPI FORTRAN compiler}} + return $mpifc + } + + # Create temporary directory + proc mktempdir {} { + set t [file join $::env(TEMP) [file rootname [file tail [info script]]][expr {int(rand()*9999)}]] + file mkdir $t + return $t + } + + # Delete directory tree + proc rmdir {dir} { + try { + file delete -force $dir + } on error {} { + # This command fires up a background sanitizing process which does its best to delete + # staging directory in spite of executable locks or what's not which can happend on Windows + # This code should work in real UNIX environments or UNIX-like Windows environments + # such as Cygwin or MSYS(2). + exec -ignorestderr sh -c "nohup \${SHELL} -c \" while \[ -d '$dir' \]; do rm -rf '$dir' || sleep 3; done\" > /dev/null 2>&1 &" + } + } + + # Execute script from a temporary location + # Script file is copied to the location along with all residing files + # The location gets deleted afterwards + proc sandbox {script} { + variable stagedir [mktempdir] + try { + ::tcltest::configure {*}$::argv + ::tcltest::workingDirectory $stagedir + file copy -force {*}[glob -directory [file dirname [file normalize [info script]]] -nocomplain *] $stagedir + eval $script + } finally { + ::tcltest::cleanupTests + rmdir $stagedir + } + } + + # Obtain compilation and linking flags for the specified packages via PkgConfig + proc require {args} { + if {[constraint? static]} {set flags --static} else {set flags {}} + xflags {*}[lindex [dict get [system [pkg-config] {*}$args --cflags {*}$flags] stdout] 0] + ldflags {*}[lindex [dict get [system [pkg-config] {*}$args --libs {*}$flags] stdout] 0] + return + } + + # Deduce source code language from command line arguments + proc deduce-language {opts} { + switch -regexp -nocase [lindex $opts [lsearch -glob -not $opts {-*}]] { + {\.c$} {return c} + {\.(cxx|cpp|cc)$} {return cxx} + {\.(f|for|f\d+)$} {return fortran} + default {error {failed to deduce source language from the command line agruments}} + } + } + + # Deduce compilation command from command line arguments + proc deduce-compiler-proc {opts} { + set lang [deduce-language $opts] + if {[constraint? mpi]} { + switch $lang { + c {return mpicc} + cxx {return mpicxx} + fortran {return mpifc} + } + } else { + switch $lang { + c {return cc} + cxx {return cxx} + fortran {return fc} + } + } + } + + # Deduce compilation flags command from command line arguments + proc deduce-compile-flags-proc {opts} { + switch [deduce-language $opts] { + c {return cflags} + cxx {return cxxflags} + fortran {return fflags} + } + } + + # C preprocessor command line arguments + proc cppflags {args} { + variable cppflags + try {set cppflags} on error {} { + set cppflags [lsqueeze [env CPPFLAGS]] + if {![constraint? debug]} {lappend cppflags -DNDEBUG} + } + lappend cppflags {*}$args + } + + # Language-agnostic compilation command line arguments + proc xflags {args} { + variable xflags + try {set xflags} on error {} { + set xflags {} + if {[constraint? openmp]} {lappend xflags -fopenmp} + if {[constraint? thread]} {lappend xflags -pthread} + if {[constraint? debug]} {lappend xflags -Og} else {lappend xflags -O2} + } + lappend xflags {*}$args + } + + # C-specific compilation command line arguments + proc cflags {args} { + variable cflags + try {set cflags} on error {} { + set cflags [lsqueeze [env CFLAGS]] + } + lappend cflags {*}$args + } + + + # C++-specific compilation command line arguments + proc cxxflags {args} { + variable cxxflags + try {set cxxflags} on error {} { + set cxxflags [lsqueeze [env CXXFLAGS]] + } + lappend cxxflags {*}$args + } + + # FORTRAN-specific compilation command line arguments + proc fflags {args} { + variable fflags + try {set fflags} on error {} { + set fflags [lsqueeze [env FFLAGS]] + } + lappend fflags {*}$args + } + + # Linker command line arguments + proc ldflags {args} { + variable ldflags + try {set ldflags} on error {} { + set ldflags [lsqueeze [env LDFLAGS]] + if {[constraint? static]} {lappend ldflags -static} + if {[constraint? openmp]} {lappend ldflags -fopenmp} + if {[constraint? thread]} {lappend ldflags -pthread} + } + lappend ldflags {*}$args + } + + # Linked libraries command line arguments + proc libs {args} { + variable libs + try {set libs} on error {} { + set libs [lsqueeze [env LIBS]] + if {[constraint? cxx]} {lappend libs -lstdc++} + if {[constraint? fortran]} {lappend libs -lgfortran -lquadmath} + } + lappend libs {*}$args + } + + # Perform source code compilation into executable + # Returns the executable name + proc build {args} { + set args [lsqueeze $args] + set exe [executable] + system {*}[concat \ + [[deduce-compiler-proc $args]] \ + -o $exe \ + [cppflags] \ + [xflags] \ + [[deduce-compile-flags-proc $args]] \ + $args \ + [ldflags] \ + [libs] \ + ] + return $exe + } + + # Perform running of the specified executable with supplied command line arguments + proc run {args} { + if {[constraint? mpi]} {set runner [mpiexec]} else {set runner {}} + system {*}[list $runner {*}$args] + } + + # Execute command line built from command line arguments + proc system {args} { + variable system-count + incr system-count + set stdout stdout${system-count} + set stderr stderr${system-count} + set args [lsqueeze $args] + set command [join $args] + if {[lsearch [::tcltest::verbose] exec] < 0} {set verbose 0} else {set verbose 1} + if {$verbose} {::puts [::tcltest::outputChannel] "> $command"} + try { + exec -ignorestderr -- {*}$args > $stdout 2> $stderr + set options {} + set status 0 + set code ok + } trap CHILDSTATUS {results options} { + set status [lindex [dict get $options -errorcode] 2] + set code error + } finally { + try { + set out [read-file $stdout] + set err [read-file $stderr] + } finally { + file delete -force $stdout $stderr + } + } + if {$verbose} { + foreach x $out {::puts [::tcltest::outputChannel] $x} + if {[llength $out] > 0 && [llength $err] > 0} {::puts [::tcltest::outputChannel] ----} + foreach x $err {::puts [::tcltest::outputChannel] $x} + } + return -code $code [dict create command $command status $status stdout $out stderr $err options $options] + } + + # Construct a scalar type ID from the constraints + proc x {} { + if {[constraint? complex]} { + if {[constraint? double]} {return z} + if {[constraint? single]} {return c} + } else { + if {[constraint? double]} {return d} + if {[constraint? single]} {return s} + } + error {failed to contstruct the scalar type} + } + + # Construct an execution model ID from the constraints + proc y {} { + if {[constraint? mpi]} {return m} + if {[constraint-any? openmp thread]} {return t} + if {[constraint? hybrid]} {return h} + return s + } + + # Construct an build type ID from the constraints + proc z {} { + if {[constraint? debug]} {return g} + return o + } + + # Construct a 3-letter XYZ build code from the constraints + proc xyz {} { + return [x][y][z] + } + + # Construct the test name based on the constraints set + proc name {} { + join [list [file rootname [file tail [info script]]] {*}[constraints]] - + } + + # Construct new unique executable name + proc executable {} { + variable build-count + variable executable + return [set executable [name]-[incr build-count].exe] + } + + # Construct human-readable description of the test according to the contraints set + proc description {} { + set t {} + switch [intersection {c cxx fortran} [constraints]] { + c {lappend t C} + cxx {lappend t C++} + fortran {lappend t FORTRAN} + } + try { + switch [y] { + s {lappend t sequential} + m {lappend t MPI} + t {lappend t multithreaded} + h {lappend t heterogeneous} + } + } on error {} {} + try { + switch [x] { + s {lappend t "single precision"} + d {lappend t "double precision"} + c {lappend t "single precision complex"} + z {lappend t "double precision complex"} + } + } on error {} {} + switch [z] { + o {lappend t optimized} + g {lappend t debugging} + } + if {[constraint? static]} {lappend t static} + join $t + } + + # Set constraints + proc constraints {args} { + variable constraints + try {set constraints} on error {} { + set constraints {} + } + lappend constraints {*}[lsqueeze $args] + } + + # Return true if any of specified contraints is set + proc constraint-any? {args} { + foreach ct $args { + if {[constraint? $ct]} {return 1} + } + return 0 + } + + # Return true if all scpecified constraints are set + proc constraint-all? {args} { + foreach ct $args { + if {![constraint? $ct]} {return 0} + } + return 1 + } + + # Return true if specified constraint is set + proc constraint? {ct} { + variable constraints + expr {[lsearch $constraints $ct] >= 0} + } + + # Test failure is triggered by throwing an exception + ::tcltest::customMatch exception {return 1; #} + + # Main test command + ::np::proc test {{name {}} {description {}} {match {exception}} -- cts script} { + foreach v {executable constraints cppflags xflags cflags cxxflags fflags ldflags libs} {variable $v; catch {unset $v}} + set cts [constraints {*}$cts] + if {$name == {}} {set name [name]} + if {$description == {}} {set description "[description] build"} + ::tcltest::test $name $description -constraints $cts -body $script -match $match + # Attempt to delete the created executable if any to conserve space in the stage dir + # It's OK for the operation to fail at this point as the executable may still be locked + variable executable; try {file delete -force $executable} on error {} {} + } + + # To be used in {all.tcl} + proc suite {args} { + ::tcltest::configure -testdir [file dirname [file normalize [info script]]] {*}$args + ::tcltest::runAllTests + ::tcltest::cleanupTests + } + + # Quick & dirty hack to introduce extra verbosity option(s) + # Override the proc from in tcltest-*.tm + # Original code corresponds to version 2.5.1 + proc ::tcltest::AcceptVerbose { level } { + set level [AcceptList $level] + set levelMap { + x exec + l list + p pass + b body + s skip + t start + e error + l line + m msec + u usec + } + set levelRegexp "^([join [dict values $levelMap] |])\$" + if {[llength $level] == 1} { + if {![regexp $levelRegexp $level]} { + # translate single characters abbreviations to expanded list + set level [string map $levelMap [split $level {}]] + } + } + set valid [list] + foreach v $level { + if {[regexp $levelRegexp $v]} { + lappend valid $v + } + } + return $valid + } + + # Read text file and return list of lines + proc read-file {file} { + set f [open $file r] + try { + return [split [read -nonewline $f] \n] + } finally { + close $f + } + } + + # Return a new list from the specified list entries squashing {} values + proc lsqueeze {list} { + set out [list] + foreach x $list { + if {$x != {}} {lappend out $x} + } + return $out + } + + # Return a new list from the specified arguments squashing {} values + proc collect {args} { + lsqueeze $args + } + + # Return intersection of two lists + proc intersection {a b} { + set x {} + foreach i $a { + if {[lsearch -exact $b $i] != -1} {lappend x $i} + } + return $x + } +} \ No newline at end of file