MATLAB:确定“命令行”中的依赖项,不包括内置依赖项

时间:2009-02-26 22:10:57

标签: matlab dependencies code-analysis

有没有办法在脚本(命令行)中使用命令确定.m文件的所有依赖关系及其调用的文件的任何依赖关系?

此前有一个类似的问题并且非常好,因为它建议使用depfun函数。但问题是它正在输出它所依赖的MATLAB相关文件。

实施例: testing.m

disp('TESTING!!');

depfun的输出('testing')

'C:\testing.m'
'C:\MATLAB\R2008a\toolbox\matlab\datatypes\@opaque\char.m'
'C:\MATLAB\R2008a\toolbox\matlab\datatypes\@opaque\double.m'
'C:\MATLAB\R2008a\toolbox\matlab\datatypes\@opaque\toChar.m'
'C:\MATLAB\R2008a\toolbox\matlab\elfun\log10.m'
'C:\MATLAB\R2008a\toolbox\matlab\elmat\ans.m'

列表有点长。

这里的要点是我希望有一些类似的功能或标志可以消除这些不需要的依赖。

7 个答案:

答案 0 :(得分:2)

当我编写一个简单的函数来创建table of contents for an m-file时,我发现这里有一些链接:

  • 讨论未记录的函数MLINTMEX
  • 的主题
  • FDEP由Urs Schwarz在MathWorks文件交换中
  • FARG由Urs Schwarz在MathWorks文件交换中
编辑:由于这个问题激起了我的好奇心,我开始尝试一些可能接近它的方法。查找非工具箱.m和.mex文件的依赖关系是相对微不足道的(我在MATLAB版本7.1.0.246中这样做了):

fcnName = 'myfile.m';
fcnList = depfun(fcnName,'-quiet');
listIndex = strmatch('C:\Program Files\MATLAB71\toolbox',fcnList);
fcnList = fcnList(setdiff(1:numel(fcnList),listIndex));

在这里,我只使用DEPFUN来获取依赖项,然后我删除了以'C:\ Program Files \ MATLAB71 \ toolbox'开头的所有文件,其中MATLAB工具箱位于我的机器上。请注意,这假设您没有将任何自己的代码放在这些MATLAB目录中(您不应该这样做)。

为了获得.mat和.txt文件的依赖关系,我采取了另一种方法。对于从上面的代码中获得的每个文件,您可以将文件的文本加载到MATLAB中并使用正则表达式解析它以查找以“.mat”或“.txt”结尾的字符串:

fid = fopen(fcnName,'rt');
fcnText = fscanf(fid,'%c');
fclose(fid);
expr = '[^\'']\''([^\''\n\r]+(?:\w\.(?:mat|txt)){1})\''[^\'']';
dataFiles = regexp(fcnText,expr,'tokens');
dataFiles = unique([dataFiles{:}]).';

我使用的正则表达式有一些限制:

  • 如果您有一个类似于'help.txt'的字符串出现在注释中(例如函数的帮助注释块),它仍然会被正则表达式检测到。我尝试使用环视操作符来解决这个问题,但这需要很长时间才能运行。

  • 如果您从变量构建字符串(例如“fileString = [someString'.mat']”),则正则表达式将无法检测到该字符串。

  • 返回的文件名字符串将是相对路径字符串。换句话说,如果函数中有'help.txt'或'C:\ temp \ junk.mat'字符串,则正则表达式匹配将返回'help.txt'或'C:\ temp \ junk.mat ',就像它们出现在函数中一样。要查找完整路径,可以在每个数据文件上使用WHICH函数(假设文件位于MATLAB路径的某处)。

希望你发现这些有用! =)

答案 1 :(得分:1)

从TMW FileExchange尝试DepSubFun

答案 2 :(得分:0)

另一种方法是排除您不需要的文件夹: localdep = depfunresult(cellfun(@ isempty,regexp(a,'toolbox'))); 您可以在那里使用任何正则表达式模式。

答案 3 :(得分:0)

感谢您到目前为止的回复。

我不认为这些是我想要完成的事情。

我希望已经有一些东西可以确定在主m文件中调用的本地函数,将它们添加到列表中,然后继续查看每个函数,直到没有剩下。似乎任何这些解决方案都没有这样做

我想出了一个我将尝试实施的方案。它可能有点蛮力,设计可能会随着我的工作而改变,但这是概念。

在这个初始设计中做了很多假设,但由于它主要针对我和其他一些人,所以我认为这对我的一般解决方案来说不是一个大问题。

要查找的文件类型:.m .mat .mex * .txt(将根据需要更新)

确定matlabpath并清除工具箱路径(这是假设您的工作目录不称为工具箱或您没有添加到其他工具箱的任何特殊m文件的地方)

希望只留下您使用的目录并可以调用函数。 (也假设你没有硬编码某种类型的[run'C:\ random \ myscript.m']

蛮力部分: 查找您感兴趣的文件类型,并列出工作目录中的文件类型(pwd)和剩余的matlab路径

删除与工作目录中的文件名匹配的文件名。

遍历搜索主m文件中的每个文件名,如果找到则将其添加到相关文件数组中。从原始列表中删除相关文件。使用“新”原始列表搜索相关文件列表,重复直到没有文件或完全没有匹配。

到目前为止,这只是我的概念,我也会再搜索一下。

答案 4 :(得分:0)

我今天终于运行了这个脚本,这是一个基于Windows matlab的脚本,因为它使'!findstr'成为“file.txt”调用。 (我更喜欢grep,但不知道matlab等效。

我将问我的老板是否允许我将其发布在matlab文件交换中以便与他人分享,所以希望我很快就会通过链接更新。

gnovice: 我没有足够的代表来评论gnovice对我在编写代码之前编写的描述的评论。

但基本上确定它所做的是取所有文件的文件名(分成文件类型的类别),剥去fullpathname和扩展名,使用上面提到的!findstr命令在.m文件中搜索它你正在构建依赖关系并将其输出到temp.txt文件(这是因为我无法找到一种方法来获得命令输出的1或0或isempty返回值)

这里是我个人搜索的细节,以确定是否使用了每个文件:

.m:'filename'或'filename('%覆盖'文件名'('case') .mex *:与上述相同 .mat:和上面做的一样,但我会改变某种负载,'filename.mat'可能会在明天工作 .txt:只搜索'filename.txt'

使用这种方法,您最终可能会得到一些额外的文本文件或.m文件,但这里的关键是应该至少拥有您需要的所有文件。

它还会在所有依赖文件上递归调用自身,以便也考虑它们的依赖关系。

-TaRDy

答案 5 :(得分:0)

我很久以前就编写了代码,用于八度音阶。我主要使用它为graphviz生成.dot文件以可视化依赖项,但我也在makefile中使用它来在编译代码时包装依赖项。不幸的是,它是perl代码,但你可以通过shell调用脚本来运行它。它是完全递归的。

要运行它,您必须将OCT_BASE更改为指向代码的根目录。 (对不起,这不是matlab的路径变量识别)。然后我可能会按照perl octavedepgrapher.pl -l <​​/ p>运行它


#! /bin/sh
exec perl -x -S $0 ${1+"$@"} # -*-perl-*-
#!perl
#
# octavedepgrapher.pl
# find the dependancy graph of octave file(s). prints a 
# dot file suitable for graphviz
# Author: steven e. pav
# Created: 2006.07.16
# SVN: $Id$
#
# * Thu Aug 30 2007 Steven Pav 
# - expanding to recognize matlabs pragma of %#function funcname
# version 0.3   2007.04.17
#  add raw output mode.
# version 0.2   2007.03.05
#  add media selection
# version 0.1   2006.08.24
#  fixed multiple functions within file.
#  added multiple edgeout capability.
#  adding clusters for files.
# version 0.0   2006.07.16
#  created.
#
#
########################################################################

########################################
# change only this
########################################

#@OCT_BASE = qw(/home/spav/sys/octave/m/ ./ $ENV{OCTAVE});
@OCT_BASE = qw(/home/spav/sys/octave/m/ ./);


########################################################################

$VERSION = "octavedepgrapher    version 0.02   2006.08.23\n";

########################################################################

use Getopt::Long;
$Getopt::Long::ignorecase = 0;
$Getopt::Long::order = $PERMUTE;

%OPT_MEANINGS = (
                 'H' => 'show Help.',
                 'l' => 'list the dependencies to standard out. do not make a dot file.',
                 'p' => 'give full path names.',
                 'm' => 'multi-edge. one for each function call.',
                 'g' => 'map connections from functions to global variables.',
                 'G' => 'map connections between functions which share global variables.',
                 'C' => 'do not cluster files.',
                 'D' => 'Debug.',
                 'd=s' => 'dependency mode for makefiles.  sets -p and -l, and but outputs in makefile suitable format. the string is the extension (with dot) to substitute for .m',
                 'r=s' => 'aspect ratio (can be fill, auto, compact (default))',
                 'B=s' => 'base directory. if given, all directories are assumed relative to this one.',
                 'L=s' => 'colon separated list of base directories of libraries (_overrides_ OCT_BASE). should probably include ./',
                 'l=s' => 'colon separated list of base directories of libraries (in addition to OCT_BASE).',
                 'X=s' => 'colon separated list of base directories to exclude in the search.',
                 'M=s' => 'media selection',
                 );

$OPTS = join('',(map { substr($_,0,1); } keys(%OPT_MEANINGS)));

&GetOptions(keys %OPT_MEANINGS);

$opt_H && &die_usage;                                       #done
$opt_L && (@OCT_BASE = split(/\s*:\s*/,$opt_L));
$opt_l && (push(@OCT_BASE,split(/\s*:\s*/,$opt_l)));
$opt_X && (@OCT_BASE = @{&rm_dirs(\@OCT_BASE,$opt_X)});

if (not $opt_M)
{ $size="25,20";
} else {
    ($opt_M =~ m/^legal/i) and $size = '8.5,14';
    ($opt_M =~ m/^letter/i) and $size = '8.5,11';

    ($opt_M =~ m/^A0$/i) and $size = '33.1,46.8';
    ($opt_M =~ m/^A1$/i) and $size = '23.4,33.1';
    ($opt_M =~ m/^A2$/i) and $size = '16.5,23.4';
    ($opt_M =~ m/^A3$/i) and $size = '11.7,16.5';
    ($opt_M =~ m/^A4$/i) and $size = '8.3,11.7';
    ($opt_M =~ m/^A4dj$/i) and $size = '8.3,11.7';
    ($opt_M =~ m/^A5$/i) and $size = '5.8,8.3';
}

#if (not $opt_r) { $ratio = 'fill'; } else { $ratio = $opt_r; }
$ratio = $opt_r || 'fill';

if ($opt_d)
{
    $opt_l = $opt_p = 1;
}

#make sure it has a tailing slash.
if ($opt_B)
{
    ($opt_B !~ m{/$}) && ($opt_B .= q[/]);
}

########################################################################

$| = 1;
if (! @ARGV)
{
    &die_usage;
} else
{
    %mfhash  = &map_name_to_filename(@ARGV);
}

if ($opt_d)
{
    @myargv     = @ARGV;
    print join(' ',map { s/\.m/$opt_d/e;$_; } @ARGV),qq[ : ];
}

if ($opt_l) {
    %bdhash = &find_base_libs(@OCT_BASE);
    $alldepref  = &find_all_deps(\%mfhash,\%bdhash,0);
    print join(' ',@{$alldepref}),qq[\n];
} else {
    &print_head();
    %bdhash = &find_base_libs(@OCT_BASE);
    &find_all_deps(\%mfhash,\%bdhash,1);
    &print_tail();
}

$opt_X && (@OCT_BASE = @{&rm_dirs(\@OCT_BASE,$opt_X)});
########################################################################
sub
    rm_dirs
    #remove directories from OCT_BASE
{
    my $ob_ref = shift(@_);
    my $oX = shift(@_);
    my @excludeus = split(/\s*:\s*/,$oX);

    #FIX!


}

########################################################################
sub
    make_relative
    #just for the sake of opt_B#FOLDUP
{
    my $fullname = shift(@_);
    if ($opt_B)
    {
        $fullname =~ s{\Q$opt_B\E}{};
    }
    return $fullname;
}#UNFOLD
########################################################################
sub
    map_name_to_filename#FOLDUP
{
    my $mfile;
    my %mfiles;
    my $mfstub;
    while ($mfile = shift(@_))
    { 
        $mfstub = $mfile;
        $mfstub =~ s/^\s*(.*\/)?([^\/]+)\.m\s*$/$2/;
        $mfiles{$mfstub} = $mfile;
    }
    return %mfiles;
}#UNFOLD

########################################################################
sub
    find_base_libs#FOLDUP
{
    my $based;
    my %bdhash;
    my ($mfile,$mfstub);
    my @mfiles;
    while ($based = shift(@_))
    { 
#           print "|$based|\n";
        @mfiles = split(/\n/,qx(cd $based && find . -name '*.m'));
        while ($mfile = shift(@mfiles))
        {
            $mfstub = $mfile;
            $mfstub =~ s/.+\/([^\/]+)\.m/$1/;
            $mfile  =~ s/^\s*\.\//$based/;
            $bdhash{$mfstub} = $mfile;
            #print STDERR "|$mfstub| -> |$mfile| |$based|\n";
        }
    }
    return %bdhash;
}#UNFOLD

########################################################################
#returns array of all the dependencies as filename strings.
sub
    find_all_deps#FOLDUP
{
    my $mfhashref = shift(@_);
    my $bdhashref = shift(@_);
    my $doprint     = shift(@_);            #if 0, do not print anything out.
    my @mfhashlist = %{$mfhashref};
    my %bdhash = %{$bdhashref};
    my $output = [];
    my %globals;
    my $gname;
    my %doneok;
    my ($mfname,$mfloc);
    my ($aline,$acommand,$copyline);
    my %eegraph;                            #store as node::node in this hash set.
                              #prevents edges from being written multiple times?
    my %dangling = {};              #any command which has yet to be found.
                              #store vals a list of things which want to point in.
    my $pointsin;
    my $foundnewfunc;
    my $foundFuncPragma;            #for looking for %  #function fname stuff
    #my @myDependencies;       #every function that I call;

    my $edgestr = '';

    while ($mfname = shift(@mfhashlist))#FOLDUP
    {
        $mfloc  = shift(@mfhashlist);
        $mf_alias = ($opt_p)? &make_relative($mfloc) : $mfname;     #full names or not

        #prevent node -> self edges.
        $eegraph{qq(${mfname}::${mfname})} = 1;

        if ((! $opt_C) && $doprint)
        {
            print qq(subgraph cluster_$mfname {\n);
            print qq(rank=min\n);
            print qq(ordering=out\n);
        }
        #node
        $doprint && 
            print qq{$mfname [label="$mf_alias" shape=plaintext fontsize=44]\n};
        push (@{$output},$mf_alias);

        $doneok{$mfname} = 1;

        #open a file#FOLDUP
        open (FH,"$mfloc") || die "no open $mfloc, $!";

         while (! eof(FH))
         {
             $aline = ;
             chomp($aline);
             $foundFuncPragma       = 0;

             if ($aline =~ /^[^%]*end\s*%?\s*function/) { $mfname = ''; }

             if ($mfname)       #inside a function
             {
                 if ($opt_g || $opt_G)          #look for globals#FOLDUP
                 {
                        if ($aline =~ /global/)
                        {
                            $copyline       = $aline;
                            while ($copyline =~ s/(global\s+)([^;\s]+)(\s*;)/$1$3/)
                            {
                                $gname  = $2;
                                if (exists $globals{$gname})
                                {
                                    push(@{$globals{$gname}},$mfname);
                                } else {
                                    $globals{$gname}    = [$mfname];
                                }
                            }
                        }
                 }#UNFOLD

                    #look for #function pragma
                 $foundFuncPragma = ($aline =~ s/%\s*#function\s+(.+)$//);
                 if ($foundFuncPragma)
                 { 
                        $opt_D && (print STDERR "found a function pragma! |$1|\n");
                        #what a bummer that we can't just use this: the
                        #problem is that we don't really know when a function
                        #ends in .m code, b/c endfunction is not required. bummer.
                        #push (@myDependencies,split(/\s+/,$1));
                        #
                        #that is, what we would really like to do is just push onto a list
                        #every time we saw a command, then puke at the end of the function,
                        #but we do not know really when a function ends in matlab. oops.
                        foreach $acommand (split(/\s+/,$1))
                        {
                            $opt_D && (print STDERR "found a command! |$acommand|\n");
                            #push (@myDependencies,$acommand);

                            if (exists($bdhash{$acommand}))
                            {
                                $opt_D && (print STDERR "exists in bdhash (prolly means is a file to itself)\n");
                                if (! $eegraph{qq(${mfname}::${acommand})})
                                {
                                    if ($opt_C) { $doprint && print "$mfname -> $acommand\n";
                                    } else { $edgestr .= "$mfname -> $acommand\n"; }

                                    if (! $opt_m) { $eegraph{qq(${mfname}::${acommand})} = 1; }
                                } 

                                if (! $doneok{$acommand})
                                {
                                    $doneok{$acommand} = 1;
                                    push(@mfhashlist,$acommand,$bdhash{$acommand});
                                }
                            } else
                            {
                                if (exists($dangling{$acommand}))
                                { push(@{$dangling{$acommand}},$mfname);
                                } else { $dangling{$acommand} = [$mfname]; }
                            }
                        }
                 }

                 while ($aline =~ /([a-zA-Z0-9_]+)\s*\(/)#FOLDUP
                 {
                    $aline =~ s/([a-zA-Z0-9_]+)\s*\(//;
                    $acommand = $1;

                    $opt_D && (print STDERR "found a command! |$acommand|\n");
                    #push (@myDependencies,$acommand);

                    if (exists($bdhash{$acommand}))
                    {
                        $opt_D && (print STDERR "exists in bdhash (prolly means is a file to itself)\n");
                        if (! $eegraph{qq(${mfname}::${acommand})})
                        {
                            if ($opt_C) { $doprint && print "$mfname -> $acommand\n";
                            } else { $edgestr .= "$mfname -> $acommand\n"; }

                            if (! $opt_m) { $eegraph{qq(${mfname}::${acommand})} = 1; }
                        } 

                        if (! $doneok{$acommand})
                        {
                            $doneok{$acommand} = 1;
                            push(@mfhashlist,$acommand,$bdhash{$acommand});
                        }
                    } else
                    {
                        if (exists($dangling{$acommand}))
                        { push(@{$dangling{$acommand}},$mfname);
                        } else { $dangling{$acommand} = [$mfname]; }
                    }
                 }#UNFOLD
             } else             #not yet inside a function.
             {
                 $foundnewfunc = 0;
                 if ($aline =~ /^[^%]*function\s+[^=]*=\s*([a-zA-Z0-9_]+)\s*(\(|;|%|$)/)
                 {
                     $mfname = $1;$foundnewfunc = 1;
                 } elsif ($aline =~ /^[^%]*function\s+([a-zA-Z0-9_]+)\s*(\(|;|%|$)/)
                 {
                     $mfname = $1;$foundnewfunc = 1;
                 } 

                 if ($foundnewfunc)
                 {
                     #@myDependencies = ();
                    $opt_D && (print STDERR "now looking at function |$mfname|\n");
                     $eegraph{qq(${mfname}::${mfname})} = 1;
                     #subnode
                     $doprint && print "$mfname [shape=box]\n";

                     $doneok{$mfname} = 1;
                     $bdhash{$mfname} = 1;              #innocent enough since doneok is set too.

                     if (exists($dangling{$mfname}))
                     {
                         while ($pointsin = shift(@{$dangling{$mfname}}))
                         {
                                $doprint && print "$pointsin -> $mfname\n";
                         }
                     }
                 }
             }
         }
        close FH;#UNFOLD
        if (! $opt_C)
        {
            $doprint && print qq(}\n);
            $doprint && print $edgestr;
            $edgestr = '';
        }
    }#UNFOLD

    if ($doprint)
    {
        if ($opt_g)
        {
            foreach $key (keys(%globals))
            {
                print qq{$key [style=dotted label="$key" color=red shape=plaintext fontsize=44]\n};
                foreach $f (@{$globals{$key}})
                {
                    print qq{$f -> $key [color=red]\n};
                }
            }
        } elsif ($opt_G)
        {
            foreach $key (keys(%globals))
            {
                while (defined($g = shift(@{$globals{$key}})))
                {
#                   foreach $f (@{$globals{$key}}) { print qq{$g -- $f [color=red]\n}; }
                    foreach $f (@{$globals{$key}}) { print qq{$g -> $f [style=dotted label="$key" fontsize=30 fontcolor=red color=red]\n}; }
                }
            }
        }
    }

    return $output;
}#UNFOLD

########################################################################

sub
    print_head#FOLDUP
{
    if (! $opt_m)
    {
        print qq[strict ];
    }
#   if ($opt_G) { print qq[octavedep {\n]; } else { print qq[digraph octavedep {\n]; }
    print qq[digraph octavedep {\n];
    print qq[nslimit=15.0\n];
    print qq[mclimit=1.0\n];
    print qq[ratio="$ratio"\n];
    print qq[size="$size"\n];
}#UNFOLD

sub
    print_tail#FOLDUP
{
    print "}\n";
}#UNFOLD

########################################################################
sub 
    die_usage#FOLDUP
{
#   print STDERR "usage: perl $0 [-$OPTS] [-$VALOPTS val] octfiles\n\n";
    print STDERR "usage: perl $0 [-$OPTS] octfiles\n\n";

    if ($opt_H) 
    {
        %OPT_MEANINGS = 
            map {($a=$_)=~s/(.)+?[=:!]?[ifs]?/$1/;$a=>$OPT_MEANINGS{$_};}    
            keys %OPT_MEANINGS;
        @OPTS = split(//,$OPTS);
        while ($OP = shift(@OPTS)) {
            print STDERR "      $OP   $OPT_MEANINGS{$OP}\n";
        }
        print STDERR "\n";
    } 


    exit;
}#UNFOLD
########################################################################
__END__

适合我...

答案 6 :(得分:0)

尽管depfun没有提供'ignore-builtins'选项,但它确实为我们提供了一个'-toponly'选项,我们可以在我们自己的递归函数中使用它,它可以激活内置函数并运行得更快。以下是我的解决方案:

function new_file_list = fastdepfun(paths)
% new_file_list = fastdepfun(paths)
% paths = same input as you use with depfun

[file_list] = depfun(paths,'-toponly','-quiet');

% Remove builtins (implement this part however you like)
mroot = matlabroot;
file_list = file_list(~strncmp(file_list,mroot,length(mroot)));

% Remove files already inspected (otherwise we get stuck in an infinite loop)
new_file_list = setdiff(file_list,paths);

if ~isempty(new_file_list)
    new_file_list = fastdepfun(new_file_list);
end
new_file_list = unique([file_list; new_file_list]);