How to run tcl script inside other tcl script? - tcl

I have two tcl scripts. I want to run the second script when the first finished. How can I do it?

Depends on what do you really mean.
One way is to write a third ("master") script which would do
source /the/path/to/the/first.tcl
source /the/path/to/the/second.tcl
Another way is to just add the second call to source from the above example to the bottom of the first script.
Amendment to the first approach: if the scripts to be executed are located in the same directory as the master script, an idiomatic way to source them is
set where [file dirname [info script]]
source [file join $where first.tcl]
source [file join $where second.tcl]
This way sourcing will work no matter what the current process's directory is and where the project directory is located.

While this is generally a correct answer, because the question was not precisely formulated there are tons of ways to achieve the goal of running Tcl code from within Tcl.
I want to get into this in detail because understanding the execution of code is one major point in understanding Tcl itself.
There is source
The source command should not be confound with executing scripts in a classical way, what I think the thread starter has asked.
The source command is like the "include" command in c/perl/php.
Languages like java or python on the other hand only have "import" mechanisms.
The difference is that those languages create a internal database of available packages, who are linked to the corresponding source/binary/bytecode files. By writing a import statement, linked source or bytecode or binary files are loaded. This allows more in-depth dependency management without writing additional code.
In Tcl this can be achieved with namespaces and the package require command.
Example:
Suppose you have this source.tcl:
proc foo {bar} {puts "baz"}
set BAM "BOO"
Now, you have your "master" script like you call it. I call it "main". It has the content:
set BAM {my important data}
source source.tcl
#also the function foo can now be used because the source reads the whole script
foo {wuz}
set BAM
#will output "BOO"
The exec command
If you can live with additional overhead of starting a whole new interpreter instance you could also do:
set BAM {my important data}
exec tclsh source.tcl
#The variable BAM will not be modified. You can not use the function foo.
The eval command
The command eval can evaluate a string or a list (in Tcl everything is a string) like it would be programmed code.
You would have to load the complete source file to a string. And then use eval, to evaluate the code within a separate scope, to not overwrite stuff in your main source file.
set fp [open "somefile" r]
set code_string [read $fp]
close $fp
eval $code_string

You just need to use source to run the 2nd script.
source "/tmp/whatever.tcl"

Simplest possible working example I could find:
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$ tclsh main.tcl
hello world
7
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$ cat main.tcl
lappend auto_path /home/thufir/NetBeansProjects/spawnTelnet/telnet/api
package require weather 1.0
tutstack::hello
set A 3
set B 4
puts [tutstack::sum $A $B]
#puts [tutstack::hello "fred"]
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$ cat api/weather.tcl
package provide weather 1.0
package require Tcl 8.5
namespace eval ::tutstack {
}
proc ::tutstack::hello {} {
puts "hello world"
}
proc ::tutstack::sum {arg1 arg2} {
set x [expr {$arg1 + $arg2}];
return $x
}
proc ::tutstack::helloWorld {arg1} {
return "hello plus arg"
}
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$ cat api/pkgIndex.tcl
# Tcl package index file, version 1.1
# This file is generated by the "pkg_mkIndex" command
# and sourced either when an application starts up or
# by a "package unknown" script. It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands. When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.
package ifneeded weather 1.0 [list source [file join $dir weather.tcl]]
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$

Related

Reading cmd arguments in TCL file

I am trying to run a tcl script through .bat file. I want to read some cmd arguments in the tcl script. Below is my code:
Command to run:
D:\Cadence\Sigrity2021.1\tools\bin\PowerSI.exe -tcl abcd.tcl %new_var%.spd %new_file_name%
Below is how I am trying to read the variable in the tcl file:
sigrity::open document [lindex $argv 0] {!}
It open up the Cadence Sigrity, but I see the below error:
How do I read cmd argument in tcl?
If you have no other way to do it that you can find (and it sounds like that might be the case) then you can fake it by writing a helper file with content like this, filling in the real arguments in the appropriate places:
# Name of script to call
set ::argv0 "abcd.tcl"
# Arguments to pass
set ::argv {}
lappend ::argv "%new_var%.spd"
lappend ::argv "%new_file_name%"
# Number of arguments (rarely used)
set ::argc [llength $::argv]
# Do the call
source $::argv0
Then you can pass that file to PowerSI and it will set things up and chain to the real file. It's messy, but practical.
If you're writing this from Tcl, use the list command to do the quoting of the strings (instead of putting them in double quotes) as it will do exactly the right thing for you. If you're writing the file from another language, you'll want to make sure you put backslashes in before \, ", $ and [ characters. The fiddlyness of doing that depends on your language.

TCL equivalent to Python's `if __name__ == "__main__"`

In one executable TCL script I'm defining a variable that I'd like to import in another executable TCL script. In Python one can make a combined library and executable by using the following idiom at the bottom of one's script:
# Library
if __name__ == "__main__":
# Executable that depends on library
pass
Is there something equivalent for TCL? There is for Perl.
The equivalent for Tcl is to compare the ::argv0 global variable to the result of the info script command.
if {$::argv0 eq [info script]} {
# Do the things for if the script is run as a program...
}
The ::argv0 global (technically a feature of the standard tclsh and wish shells, or anything else that calls Tcl_Main or Tk_Main at the C level) has the name of the main script, or is the empty string if there is no main script. The info script command returns the name of the file currently being evaluated, whether that's by source or because of the main shell is running it as a script. They'll be the same thing when the current script is the main script.
As mrcalvin notes in the comments below, if your library script is sometimes used in contexts where argv0 is not set (custom shells, child interpreters, embedded interpreters, some application servers, etc.) then you should add a bit more of a check first:
if {[info exists ::argv0] && $::argv0 eq [info script]} {
# Do the things for if the script is run as a program...
}
I recently wanted this functionality to set up some unit tests for my HDL build scripts suite. This is what i ended up with for Vivado:
proc is_main_script {} { ;# +1 frame
set frame [info frame [expr [info frame] -3]]
if {![dict exists $frame file]} {
set command [file normalize [lindex [dict get $frame cmd] 1]]
set script [file normalize [info script]]
if {$script eq $command} {
return 1
} else {
return 0
}
} else {
return 0
}
}
if {is_main_script} { ;# +1 frame
puts "do your thing"
}
As I consider this for test/demo i consider the main use case to be something in the line with if {is_main_script} {puts "do something"} "un nested" at the end of the file.
If a need to make it more general a dynamic handle for the frame reference -3 could probably be developed. All though this has covered all my use cases so far.
frame -3 is used as proc and if creates extra frames and to evaluate this we want to check the call before.
dict exists is used to check if file exists within the frame. This would indicate the call was from a higher hierarchical level script and would there for not be the "main_script"
The solution if {[info exists ::argv0] && $::argv0 eq [info script]} works great if run as vivado -source TCLSCRIPT.tcl but the solution above covers source TCLSCRIPT.tcl in gui or tcl mode (this is something i often se my self doing when debugging a automation tcl).
I guess this is a niche case. But since I couldn't find any other solution for this problem I wanted to leave this here.

Command to return library (not work) name of a path in modelsim

I want to find a way to return the name of a library of a certain path in a VHDL Design in Modelsim.
Given a VHDL Design with a path like "/mega_tb/D0". This is compiled in a library that is NOT 'work', say "libnwork".
I can of course take a look in my 'do' file to get the correct lib name. Or I can search in ModelSim's Library tab. But I want to have or create a modelsim command which I can later use in a Tcl script, to get the correct library name.
One of the easiest ways to find something in a Tcl script file – which is all a Modelsim “do” file is — is to evaluate it. Tcl's very good at that. Of course, you don't want to have the commands do all the conventional things. Instead, we'll evaluate in a context where we can make everything do nothing except for the command that produces the information we want:
# Set up our evaluation context, 'worker'
interp create worker -safe
interp eval worker {proc unknown args {}}; # Our do-nothing handler
interp alias worker theInterestingCommand {} ourHandler
proc ourHandler args {
puts "We were called with: $args"
}
# Parse the file!
set f [open /the/file.tcl]
interp eval worker [read $f]
# Clean up
close $f
interp delete worker
Now you just have to make theInterestingCommand have the right name and extract the interesting information from the arguments. Which should be relatively easy…
Te only way I've found is to use the command
write report -tcl
This prints a long list where I have search for the lib names with regexps.
Something like
set data [ write report -tcl]
foreach_regexp { _ type lib entity} $data{
if {$type == "Entity" && $entity == [entity_of_path /mega_tb/D0] } {
....
}
}
Where I of course had to define my "foreach_regexp" procedure and my "entity_of_path" procedure. I then can use something like regsub to extract the library name.
I am still looking for a better and easier way.

Printing out source hierarchy with large TCL project

So I am working with a large TCL project and thought it would be cool to build a treeview of how files were being source in the project. I modified the source command to do the following:
rename ::source ::real_source
proc ::source args {
set file_handle [open "file_source.tcl" a]
puts $file_handle $args
puts $file_handle $argv0
close $file_handle
uplevel 1 ::real_source $args
}
Which works and saves all of the files being sourced but I was wondering if anyone had any ideas on how I could determine which files are calling the source command?
Another interesting issue I am running into is that my new source procedure seems to only work in some files. File A sources File B and all of the sources in File B seem to work correctly but anything under that seems to go back to using the old source procedure. Any ideas on why this is happening?
[info script] will give you the name of the file invoking source
Example:
a.tcl
rename ::source ::real_source
proc ::source args {
puts "[info script] sources $args"
uplevel 1 ::real_source $args
}
source b.tcl
b.tcl
puts "in file b"
source c.tcl
c.tcl
puts "in file c"
outputs
a.tcl sources b.tcl
in file b
b.tcl sources c.tcl
in file c

Getting path of the proc in TCL which is invoked from another script

i am a newbie in TCL Programming
I am having a tcl script called test1.tcl and test2.tcl separately in two different
directories F:\TCLPrograms\SamplePrograms\test1.tcl and F:\TCLPrograms\test2.tcl
i want to know the full path of test2.tcl which is a proc
if i give info [script] inside proc disp {} its returning the path from where it is invoked
i.e F:\TCLPrograms\SamplePrograms\test1.tcl
kindly someone tell me to get the path of the proc
test1.tcl:
puts "Processing test1..."
source "F:\\TCLPrograms\\test2.tcl"
set rc [disp]
puts "Executed...."
test2.tcl:
proc disp { } {
puts "Successfully executed test2.tcl"
set path [info script]
puts "Script is invoked from the path: $path"
}
Thanks in advance
The result of info script depends on the current innermost source, and procedures don't maintain that information. (Well, it's maintained in debugging information for 8.6 and some builds of 8.5 from ActiveState, but it's truly awkward to access.)
The easiest way is to use a variable to hold the name of the file, like this:
variable dispScriptFile [file normalize [info script]]
proc disp {} {
variable dispScriptFile
puts "Successfully executed test2.tcl"
set path [file dirname $dispScriptFile]
puts "Script is invoked from the path: $path"
}
Note that we use the normalized filename, so that it remains valid even if you use a relative pathname and then cd to some other directory.
(I also recommend putting the whole contents of test2.tcl inside its own namespace; it makes it easier to keep things separate.)