checkin.pl 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. #!/usr/bin/perl
  2. use warnings;
  3. use strict;
  4. use Cwd qw(getcwd abs_path);
  5. use File::Spec;
  6. use POSIX qw(WEXITSTATUS);
  7. open(RVV, '<', 'RepoVivadoVersion');
  8. my $VIVADO_VERSION = <RVV>; chomp $VIVADO_VERSION;
  9. close(RVV);
  10. unless ($VIVADO_VERSION) {
  11. printf "Unable to detect the Vivado version in use in this repository.\n";
  12. exit(1);
  13. }
  14. if ($ENV{PATH} !~ m!/Vivado/\Q$VIVADO_VERSION\E(/\.)?/bin!) {
  15. printf "You are not running Vivado $VIVADO_VERSION or have not sourced the environment initialization scripts. Aborting.\n";
  16. exit(1);
  17. }
  18. our $DEBUG = 0;
  19. our $SAVE_RAW_TCL = 0;
  20. my %MESSAGES;
  21. my %PROJECT_REPO_DEPS;
  22. for my $ProjectPath (glob("workspace/*/*.xpr")) {
  23. next unless $ProjectPath =~ m!^workspace/([^/]+)/([^/]+)\.xpr$!;
  24. my $ProjectFilename = $2;
  25. my $ProjectCanonicalName = $1;
  26. my $SourcesBdDir = sprintf("sources/%s.bd", $ProjectCanonicalName);
  27. my $ProjectDir = sprintf("%s/workspace/%s", getcwd(), $ProjectCanonicalName);
  28. $MESSAGES{$ProjectCanonicalName} = [];
  29. mkdir('sources');
  30. mkdir(sprintf("sources/%s", $ProjectCanonicalName));
  31. mkdir($SourcesBdDir);
  32. unlink(glob("$SourcesBdDir/*"));
  33. printf "~"x80 ."\n";
  34. printf "~~~ Processing Project: %s\n", $ProjectCanonicalName;
  35. printf "~~~\n";
  36. printf "~~~ Exporting Project TCL from Vivado\n";
  37. open(VIVADO, '|-', 'vivado', '-nojournal', '-nolog', '-mode', 'tcl', $ProjectPath);
  38. printf VIVADO "write_project_tcl -force \".exported.tcl\"\n";
  39. printf VIVADO "foreach {bd_file} [get_files -filter {FILE_TYPE == \"Block Designs\"}] {\n";
  40. printf VIVADO " open_bd_design \$bd_file\n";
  41. printf VIVADO " write_bd_tcl \"%s/[file rootname [file tail \$bd_file]].tcl\"\n", $SourcesBdDir;
  42. printf VIVADO " close_bd_design [file rootname [file tail \$bd_file]]\n";
  43. printf VIVADO "}\n";
  44. close(VIVADO);
  45. if (WEXITSTATUS($?)) {
  46. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 1, Severity => 'CRITICAL ERROR', Message => sprintf("Vivado exited with an unexpected status code after project export: %s. Aborting. The project has NOT been exported or updated!", WEXITSTATUS($?)) };
  47. unlink('.exported.tcl');
  48. next;
  49. }
  50. printf "\n";
  51. printf "~~~ Analyzing & Rewriting Project TCL and Copying source files\n";
  52. process_tcl('.exported.tcl', '.processed.tcl', $ProjectCanonicalName, $ProjectDir);
  53. rename('.processed.tcl', sprintf("sources/%s.tcl", $ProjectCanonicalName));
  54. rename('.exported.tcl', sprintf("sources/%s.tcl.raw", $ProjectCanonicalName)) if ($DEBUG || $SAVE_RAW_TCL);
  55. unlink('.processed.tcl');
  56. unlink('.exported.tcl');
  57. printf "\n";
  58. printf "~~~\n";
  59. printf "~~~ Finished processing project %s\n", $ProjectCanonicalName;
  60. printf "~"x80 ."\n";
  61. if (@{$MESSAGES{$ProjectCanonicalName}}) {
  62. printf "~~~ MESSAGES FOR PROJECT %s\n", $ProjectCanonicalName;
  63. printf "~~~\n";
  64. for my $Message (@{$MESSAGES{$ProjectCanonicalName}}) {
  65. if ($DEBUG) {
  66. printf "~~~ [%4d] %s: %s\n", $Message->{Line}, $Message->{Severity}, $Message->{Message};
  67. } else {
  68. printf "~~~ %s: %s\n", $Message->{Severity}, $Message->{Message};
  69. }
  70. }
  71. printf "~~~\n";
  72. }
  73. printf "\n";
  74. }
  75. if (-e 'workspace/ip_repo') {
  76. printf "\n";
  77. printf "~"x80 ."\n";
  78. printf "~~~ COPYING IP_REPO\n";
  79. printf "~~~\n";
  80. system('rsync',
  81. '-rhtci', '--del',
  82. 'workspace/ip_repo/',
  83. 'ip_repo_sources/');
  84. printf "~~~\n";
  85. printf "\n";
  86. }
  87. open(DEPORDER, '>', 'projects.list');
  88. printf DEPORDER "%s\n", join "\n", process_deps(%PROJECT_REPO_DEPS);
  89. close(DEPORDER);
  90. my $Worry = 0;
  91. my %MessageTotals;
  92. for my $ProjectCanonicalName (keys %MESSAGES) {
  93. if (@{$MESSAGES{$ProjectCanonicalName}}) {
  94. printf "\n\n" unless (%MessageTotals);
  95. printf "~"x80 ."\n";
  96. printf "~~~ MESSAGES FOR PROJECT %s\n", $ProjectCanonicalName;
  97. printf "~~~\n";
  98. for my $Message (@{$MESSAGES{$ProjectCanonicalName}}) {
  99. if ($DEBUG) {
  100. printf "~~~ [%4d] %s: %s\n", $Message->{Line}, $Message->{Severity}, $Message->{Message};
  101. } else {
  102. printf "~~~ %s: %s\n", $Message->{Severity}, $Message->{Message};
  103. }
  104. $MessageTotals{$Message->{Severity}} = 0 unless exists($MessageTotals{$Message->{Severity}});
  105. $MessageTotals{$Message->{Severity}}++;
  106. $Worry++ if ($Message->{Hazard});
  107. }
  108. printf "~~~\n";
  109. }
  110. }
  111. if (grep { $_ } values %MessageTotals) {
  112. printf "~"x80 ."\n";
  113. printf "\n";
  114. system("git", "status");
  115. printf "\n";
  116. for my $MessageType (sort keys %MessageTotals) {
  117. printf "~~~ %u %s messages\n", $MessageTotals{$MessageType}, $MessageType;
  118. }
  119. printf "\n";
  120. printf "~~~ Please review them carefully and make sure none are dangerous before committing.\n";
  121. printf "~~~ Always review 'git status' before committing!.\n";
  122. exit(2);
  123. }
  124. else {
  125. printf "~~~ No issues encountered. Projects exported and ready to add to git.\n";
  126. printf "~~~ Always review 'git status' before committing!.\n";
  127. printf "\n";
  128. system("git", "status");
  129. exit(0);
  130. }
  131. sub process_tcl {
  132. my $TclInFile = shift;
  133. my $TclOutFile = shift;
  134. my $ProjectCanonicalName = shift;
  135. my $ProjectDir = shift;
  136. $PROJECT_REPO_DEPS{$ProjectCanonicalName} = [];
  137. if (!open(TCLOUT, '>', $TclOutFile)) {
  138. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 1, Severity => 'CRITICAL ERROR', Message => sprintf("Unable to open intermediate file \"%s\". Aborting. The project has NOT been exported or updated!", $TclOutFile) };
  139. unlink($TclOutFile);
  140. return;
  141. }
  142. if (!open(TCLIN, '<', $TclInFile)) {
  143. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 1, Severity => 'CRITICAL ERROR', Message => sprintf("Unable to open intermediate file \"%s\". Aborting. The project has NOT been exported or updated!", $TclInFile) };
  144. close(TCLOUT);
  145. unlink($TclOutFile);
  146. return;
  147. }
  148. my %SourcesIndex;
  149. my %TargetsIndex;
  150. my $FileListing = 0;
  151. my $InitialComments = 1;
  152. my $LastComment = "";
  153. my $BD_Inject_State = 0;
  154. my @Discarded_BD_Wrappers;
  155. my $FileList_State = 0;
  156. my $FileProperty_Disarm = 0;
  157. my $Current_SetFile = undef;
  158. while (my $Line = <TCLIN>) {
  159. my $KeepLine = 1;
  160. my $PathSubstitute = 1;
  161. chomp $Line;
  162. if ($InitialComments...($Line =~ /^(?!#)/)) {
  163. # Remove inital comments for git consistency.
  164. $InitialComments = 0;
  165. $KeepLine = 0 unless ($DEBUG);
  166. }
  167. if ($Line =~ /^#\s+(.*)$/) {
  168. $LastComment = $1;
  169. }
  170. if ($Line =~ /^set orig_proj_dir /) {
  171. $Line = sprintf('set orig_proj_dir "[file normalize "sources/%s"]"', $ProjectCanonicalName);
  172. }
  173. if ($Line =~ /^create_project /) {
  174. $Line = sprintf('create_project %s workspace/%s', $ProjectCanonicalName, $ProjectCanonicalName);
  175. }
  176. if ($Line =~ /^set obj \[get_projects \S+\]/) {
  177. $Line = sprintf('set obj [get_projects %s]', $ProjectCanonicalName);
  178. }
  179. if (($FileListing == 0 && $Line =~ /^# 2\. The following source\(s\) files that were local or imported into the original project/)...($Line =~ /^\s*$/)) {
  180. $FileListing = 1;
  181. if ($Line =~ /^#\s+"(.*)"$/) {
  182. my $RawFile = $1;
  183. my $File = get_path($RawFile, 1, $ProjectCanonicalName, 0, undef);
  184. if ($File =~ m!\.srcs/[^ /]+/bd/([^ /]+)/\1.bd$!) {
  185. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 0, Severity => 'INFO', Message => sprintf("Discarding source file (Block Designs exported separately): %s", $File) };
  186. }
  187. elsif ($File =~ m!\.srcs/[^ /]+/bd/([^ /]+)/hdl/\1_wrapper\.v(?:hd)?$!) {
  188. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 0, Severity => 'INFO', Message => sprintf("Discarding source file (Block Design auto-wrapper will be regenerated): %s", $File) };
  189. push @Discarded_BD_Wrappers, $1;
  190. $SourcesIndex{abs_path($File)} = undef;
  191. }
  192. else {
  193. my $Target = get_path($RawFile, 1, $ProjectCanonicalName, 1, undef);
  194. if (!-e $File) {
  195. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 1, Severity => 'WARNING', Message => sprintf("Unable to locate/copy specified source file: %s", $1) };
  196. }
  197. else {
  198. if ($File ne $Target) {
  199. if (exists($TargetsIndex{abs_path($Target)}) && $TargetsIndex{abs_path($Target)} != abs_path($File)) {
  200. $Target = get_new_file_target($Target);
  201. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 1, Severity => 'CRITICAL WARNING', Message => sprintf("DUPLICATE TARGET FILE in repository sources: \"%s\" is being remapped to \"%s\" in violation of the pattern. The scripts MAY not perfectly handle this case through future iterations of checkin. Be attentive!", $File, $Target) };
  202. }
  203. my $TargetDir = $Target;
  204. $TargetDir =~ s#/[^/]+$##;
  205. system('mkdir', '-p', '--', $TargetDir);
  206. system('cp', '-a', '--', $File, $Target);
  207. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 0, Severity => 'INFO', Message => sprintf("Relocating file to repository sources: %s", $File) };
  208. }
  209. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 0, Severity => 'DEBUG INFO', Message => sprintf("Registering Target: %s", $Target) } if ($DEBUG);
  210. $SourcesIndex{abs_path($File)} = abs_path($Target);
  211. $TargetsIndex{abs_path($Target)} = abs_path($File);
  212. }
  213. }
  214. }
  215. }
  216. if ($Line =~ /^set files \[list \\$/) {
  217. $FileList_State = 1; # In list.
  218. }
  219. if ($FileList_State) {
  220. if ($Line =~ /^\s*$/) {
  221. $FileList_State = 0; # Reset state, we are finished.
  222. }
  223. elsif ($Line =~ m![^ /]+\.srcs/[^ /]+/bd/([^ /]+)/\1.bd"! ||
  224. $Line =~ m![^ /]+\.srcs/[^ /]+/bd/([^ /]+)/hdl/\1_wrapper\.v(?:hd)?"!) {
  225. $KeepLine = 0;
  226. }
  227. elsif ($Line =~ /^\s+"\[file normalize "(.*)"\]"\\$/) {
  228. my $File = get_path($1, 1, $ProjectCanonicalName, 1, \%SourcesIndex);
  229. if (-e $File) {
  230. $FileList_State = 2; # Found a valid file to keep.
  231. }
  232. else {
  233. $KeepLine = 0;
  234. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 1, Severity => 'CRITICAL WARNING', Message => sprintf("Unable to locate/register specified source file \"%s\". File excluded.", $File) };
  235. }
  236. }
  237. elsif ($Line =~ /^add_files /) {
  238. if ($FileList_State == 1) {
  239. # We didnt have any actual files in this list, that we kept.
  240. # Disable the add_files, as it will error.
  241. $Line = "# $Line";
  242. }
  243. $FileList_State = 0; # And finish.
  244. }
  245. elsif ($Line =~ /^set imported_files /) {
  246. if ($FileList_State == 1) {
  247. # We didnt have any actual files in this list, that we kept.
  248. # Disable the import_files, as it will error.
  249. $Line = "# $Line";
  250. }
  251. else {
  252. if ($Line =~ /set imported_files \[import_files -fileset (\S+) /) {
  253. $Line = sprintf("add_files -norecurse -fileset [get_filesets %s] \$files", $1);
  254. }
  255. else {
  256. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 1, Severity => 'WARNING', Message => sprintf("Unable to parse import_files fileset parameter in \"%s\". Files will be imported, not added. Report the bug.", $Line) };
  257. }
  258. }
  259. $FileList_State = 0; # And finish.
  260. }
  261. }
  262. if ($Line =~ m!^set file "(.*)"$!) {
  263. $Current_SetFile = $1;
  264. if ($Current_SetFile =~ /^\[file normalize "(.*)"\]$/) {
  265. $Current_SetFile = $1;
  266. }
  267. $FileProperty_Disarm = 2 unless (-e get_path($1, 1, $ProjectCanonicalName, 0, \%SourcesIndex));
  268. }
  269. elsif ($Line =~ /^\s*$/) {
  270. $Current_SetFile = undef;
  271. }
  272. if ($Line =~ m!^set file ".*\.srcs/[^ /]+/bd/([^ /]+)/hdl/\1_wrapper\.v(?:hd)?"$!) {
  273. $FileProperty_Disarm = 1; # Property block is to be discarded.
  274. }
  275. if ($Line =~ m!^set file ".*\.srcs/[^ /]+/bd/([^ /]+)/\1\.bd"$!) {
  276. $FileProperty_Disarm = 1; # Property block is to be discarded.
  277. }
  278. if ($FileProperty_Disarm) {
  279. if ($Line =~ /^\s*$/) {
  280. $FileProperty_Disarm = 0; # Reset state, we are finished.
  281. }
  282. elsif ($FileProperty_Disarm == 1) {
  283. $KeepLine = 0;
  284. }
  285. elsif ($FileProperty_Disarm == 2) {
  286. $Line = "# $Line";
  287. $PathSubstitute = 0;
  288. }
  289. }
  290. elsif ($Line =~ /^set file_imported \[import_files -fileset (\S+) \$file\]$/) {
  291. $Line = sprintf("add_files -norecurse -fileset [get_filesets %s] \$file", $1);
  292. }
  293. # Vivado 2014.4 searches the entire directory recursively for IP-XACT files, this symlink is no longer required. (Was it before..?)
  294. #
  295. # if ($Line =~ /^set_property "file_type" "IP-XACT" \$file_obj$/ && $Current_SetFile =~ m!workspace\/\Q$ProjectCanonicalName\E/component\.xml$!) {
  296. # #$origin_dir/workspace/v7_loader/v7_loader.srcs/sources_1/imports/imports/component.xml
  297. # printf TCLOUT "file link -symbolic workspace/%s/component.xml ../../sources/%s/component.xml\n", $ProjectCanonicalName, $ProjectCanonicalName;
  298. # }
  299. if ($Line =~ /^set_property "file_type" "Unknown" \$file_obj$/) {
  300. $Line = "#$Line";
  301. }
  302. if ($BD_Inject_State == 0 && $Line =~ /^# Set \S+ fileset object$/) {
  303. $BD_Inject_State = 1;
  304. if (glob(sprintf("sources/%s.bd/*.tcl", $ProjectCanonicalName))) {
  305. printf TCLOUT "puts \"*** BEGINNING TO RECONSTRUCT BLOCK DESIGNS\"\n";
  306. printf TCLOUT "foreach {bd_file} [glob sources/%s.bd/*] {\n", $ProjectCanonicalName;
  307. printf TCLOUT " source \$bd_file\n";
  308. printf TCLOUT "}\n";
  309. foreach my $BD_Wrapper (@Discarded_BD_Wrappers) {
  310. printf TCLOUT "add_files -norecurse -force [make_wrapper -files [get_files %s.bd] -top]\n", $BD_Wrapper;
  311. }
  312. if (@Discarded_BD_Wrappers) {
  313. printf TCLOUT "foreach {fileset} [get_filesets -filter {FILESET_TYPE =~ {*Srcs}}] {\n";
  314. printf TCLOUT " update_compile_order -fileset \$fileset\n";
  315. printf TCLOUT "}\n";
  316. }
  317. printf TCLOUT "puts \"*** FINISHED RECONSTRUCTING BLOCK DESIGNS\"\n";
  318. printf TCLOUT "\n";
  319. }
  320. }
  321. if ($Line =~ /^set_property "ip_repo_paths" "(.*)" \$obj$/) {
  322. printf TCLOUT "# $Line\n";
  323. # set_property "ip_repo_paths" "[file normalize "$origin_dir/../workspace/mmcspi"] [file normalize "$origin_dir/../workspace/v7_loader"]" $obj
  324. # set_property "ip_repo_paths" "[file normalize "$origin_dir/workspace/startup_override/startup_override.srcs/sources_1/new"]" $obj
  325. my $RepoPaths = $1;
  326. $PathSubstitute = 0;
  327. my @RepoPaths = split /(?<=\]) (?=\[)/, $RepoPaths;
  328. my @NewRepoPaths;
  329. foreach my $RepoPath (@RepoPaths) {
  330. unless ($RepoPath =~ /^\[file normalize "([^"]+)"\]$/) {
  331. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 1, Severity => 'WARNING', Message => sprintf("Ignoring unparsable ip_repo_path: %s", $RepoPath) };
  332. next;
  333. }
  334. $RepoPath = get_path($1,1,$ProjectCanonicalName,0,undef);
  335. next unless ($RepoPath);
  336. if ($RepoPath !~ m!^workspace/ip_repo!) {
  337. $RepoPath =~ s/^workspace\//sources\//;
  338. }
  339. if (!($RepoPath =~ m!^sources/([^/]+)(?:/.*)?$!)) {
  340. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 1, Severity => 'WARNING', Message => sprintf("External ip_repo_path \"%s\" will NOT be processed! Strongly consider relocating it to workspace/ip_project", $RepoPath) };
  341. }
  342. elsif ($1 ne $ProjectCanonicalName) {
  343. #push @{$PROJECT_REPO_DEPS{$ProjectCanonicalName}}, $1;
  344. }
  345. push @NewRepoPaths, sprintf('[file normalize "%s"]', $RepoPath);
  346. }
  347. $Line = sprintf('set_property "ip_repo_paths" "%s" $obj', join(" ", @NewRepoPaths));
  348. }
  349. if ($PathSubstitute) {
  350. # It appears to outright lie, saying that the file is relative to $origin_dir when it is actually relative to sources/$ProjectCannonicalName
  351. # set_property "steps.write_bitstream.tcl.pre" "[file normalize "$origin_dir/CTP7.srcs/sources_1/imports/CTP7/ignore_LUTLP1.tcl"]" $obj
  352. if ($Line =~ /^set_property "steps\.([^"]+)\.tcl\.(pre|post)" "\[file normalize "\$origin_dir\/([^[\/\$].*)"]" \$obj$/) {
  353. if (-e "sources/$ProjectCanonicalName/$3") {
  354. $Line = sprintf('set_property "steps.%s.tcl.%s" "[file normalize "sources/%s/%s"]" $obj', $1, $2, $ProjectCanonicalName, $3);
  355. }
  356. }
  357. $Line =~ s/"(\$(?:origin_dir|proj_dir)\/[^"]*)"/sprintf('"%s"', get_path($1,1,$ProjectCanonicalName,1,\%SourcesIndex))/eg;
  358. if ($Line =~ /^set_property "steps\.([^"]+)\.tcl\.(pre|post)" "([^[\/\$].*)" \$obj$/) {
  359. $Line =~ s/" "/" "[pwd]\//;
  360. }
  361. }
  362. printf TCLOUT "%s\n", $Line if $KeepLine;
  363. }
  364. printf "\n";
  365. printf "~~~ Updating any component.xml files\n";
  366. open(FINDXML, '-|', 'find', sprintf('sources/%s', $ProjectCanonicalName), '-type', 'f', '-name', 'component.xml');
  367. while (my $ComponentXML = <FINDXML>) {
  368. chomp $ComponentXML;
  369. my $ComponentPath = $ComponentXML;
  370. $ComponentPath =~ s#/component\.xml$##;
  371. printf "~~~ Processing %s\n", $ComponentXML;
  372. my $WSPath = $ComponentPath;
  373. $WSPath =~ s/^sources/workspace/;
  374. if ($WSPath ne $ComponentPath && -d sprintf("%s/xgui/", $WSPath)) {
  375. system('rsync', '-ahv', '--del', sprintf("%s/xgui/", $WSPath), sprintf("%s/xgui/", $ComponentPath));
  376. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 0, Severity => 'INFO', Message => sprintf("Relocating directory to repository sources: workspace/%s/xgui", $ProjectCanonicalName) };
  377. }
  378. process_componentxml($ComponentXML, '.component.xml', $ProjectCanonicalName, $ComponentPath, \%TargetsIndex);
  379. rename('.component.xml', $ComponentXML);
  380. unlink('.component.xml');
  381. }
  382. close(FINDXML);
  383. printf "\n";
  384. printf "~~~ Purging unused sources\n";
  385. open(FIND, '-|', 'find', sprintf('sources/%s', $ProjectCanonicalName), '-type', 'f');
  386. while (my $File = <FIND>) {
  387. chomp $File;
  388. $File = abs_path($File);
  389. unlink($File) unless (exists($TargetsIndex{$File}));
  390. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 0, Severity => 'DEBUG WARNING', Message => sprintf("Unlinked \"%s\".", $File) } if ($DEBUG && !exists($TargetsIndex{$File}));
  391. }
  392. close(FIND);
  393. system('find', sprintf('sources/%s', $ProjectCanonicalName), '-depth', '-type', 'd', '-exec', 'rmdir', '--ignore-fail-on-non-empty', '{}', ';');
  394. }
  395. sub process_componentxml {
  396. my $XML_In = shift;
  397. my $XML_Out = shift;
  398. my $ProjectCanonicalName = shift;
  399. my $ComponentPath = shift;
  400. my $TargetsIndex = shift;
  401. if (!open(XMLOUT, '>', $XML_Out)) {
  402. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 1, Severity => 'CRITICAL ERROR', Message => sprintf("Unable to open intermediate file \"%s\". Aborting. This project's IP package may be unusuable!", $XML_Out) };
  403. unlink($XML_Out);
  404. return;
  405. }
  406. if (!open(XMLIN, '<', $XML_In)) {
  407. push @{$MESSAGES{$ProjectCanonicalName}}, { Line => __LINE__, Hazard => 1, Severity => 'CRITICAL ERROR', Message => sprintf("Unable to open intermediate file \"%s\". Aborting. This project's IP package may be unusable!", $XML_In) };
  408. close(TCLOUT);
  409. unlink($XML_Out);
  410. return;
  411. }
  412. while (my $Line = <XMLIN>) {
  413. chomp $Line;
  414. if (($Line =~ /^\s*<spirit:file>$/)...($Line =~ /^\s*<\/spirit:file>$/)) {
  415. if ($Line =~ /^(\s*)<spirit:name>(.*)<\/spirit:name>$/) {
  416. my $Indent = $1;
  417. my $File = abs_path(File::Spec->rel2abs($2, $ComponentPath));
  418. if ($File) {
  419. $Line = sprintf("%s<spirit:name>%s</spirit:name>", $Indent, File::Spec->abs2rel($File, $ComponentPath));
  420. $TargetsIndex->{$File} = $File;
  421. }
  422. }
  423. }
  424. printf XMLOUT "%s\n", $Line;
  425. }
  426. }
  427. sub _get_path_update_for_xcix {
  428. # Vivado 2016.1 will report files as /ip/module/module.xci when they are actually packaged as /ip/module.xcix
  429. my $Path = shift;
  430. return $Path unless $Path =~ /\.xci$/;
  431. return $Path if -e $Path;
  432. my $AltPath = $Path;
  433. $AltPath =~ s!/ip/([^/]+)/\g1\.xci!/ip/$1.xcix!;
  434. return $AltPath if -e ($AltPath);
  435. my $SourcePath = $AltPath;
  436. $SourcePath =~ s!(^|/)workspace/!$1sources/!;
  437. return $AltPath if -e ($SourcePath);
  438. return $Path;
  439. }
  440. sub get_path {
  441. my $Path = shift;
  442. my $Relative = shift;
  443. my $Project = shift;
  444. my $TargetSources = shift;
  445. my $SourcesIndex = shift;
  446. $Path =~ s/\$origin_dir\//.\//;
  447. $Path =~ s/\$proj_dir\//workspace\/$Project\//;
  448. my $OrigPath = $Path;
  449. $Path = _get_path_update_for_xcix($Path) if defined($Path);
  450. $Path = abs_path($Path);
  451. if (!defined($Path) || ! -e($Path)) {
  452. if (defined $SourcesIndex) {
  453. foreach my $SourcePath (keys %$SourcesIndex) {
  454. if ($SourcePath =~ /(^|\/|(?=\/))\Q$OrigPath\E$/) {
  455. $SourcePath = _get_path_update_for_xcix($SourcePath) if defined($SourcePath);
  456. $SourcePath = abs_path($SourcePath);
  457. if (!defined($SourcePath) || ! -e($SourcePath)) {
  458. next;
  459. }
  460. else {
  461. $Path = $SourcePath;
  462. last;
  463. }
  464. }
  465. }
  466. }
  467. if (!defined($Path) || ! -e($Path)) {
  468. push @{$MESSAGES{$Project}}, { Line => __LINE__, Hazard => 1, Severity => 'WARNING', Message => sprintf("Missing path: %s", $OrigPath) };
  469. return '';
  470. }
  471. }
  472. my $pwd = abs_path(getcwd());
  473. if (defined($Project) && $TargetSources) {
  474. if ($Path =~ m!^\Q$pwd\E/(?:workspace|sources)/([^/]+)/(.*)!) {
  475. $Path = sprintf("%s/sources/%s/%s", $pwd, $1, $2);
  476. }
  477. else {
  478. $Path = sprintf("%s/sources/%s/%s", $pwd, $Project, $Path);
  479. }
  480. }
  481. if ($Relative) {
  482. $Path = File::Spec->abs2rel($Path, '.');
  483. }
  484. return $Path;
  485. }
  486. sub get_new_file_target {
  487. my $Target = shift;
  488. my $Int = 0;
  489. my $Ext = '';
  490. if ($Target =~ /^(.+)(\.[^\/.]+)$/) {
  491. $Target = $1;
  492. $Ext = $2;
  493. }
  494. if ($Ext =~ /^\.[0-9]+$/) {
  495. $Target = $Target . $Ext;
  496. $Ext = '';
  497. }
  498. if ($Target =~ /^(.+)\.([0-9]+)$/) {
  499. $Target = $1;
  500. $Int = int($2);
  501. }
  502. while ($Int < 500) {
  503. my $Candidate = sprintf("%s.%u%s", $Target, $Int, $Ext);
  504. return $Candidate unless (-e $Candidate);
  505. $Int++;
  506. }
  507. die(sprintf("Unable to produce suitable alternate target file candidate! Target: \"%s\", Ext: \"%s\"", $Target, $Ext));
  508. }
  509. sub process_deps {
  510. my %Deps = @_;
  511. my %Seen = map { ($_,0) } keys %Deps;
  512. my @Order;
  513. my $Fail = 0;
  514. sub do_procdep {
  515. my $Token = shift;
  516. my $Deps = shift;
  517. my $Seen = shift;
  518. my $Order = shift;
  519. my $Fail = shift;
  520. return if ($Seen->{$Token} == 2);
  521. if ($Seen->{$Token} == 1) {
  522. $$Fail = 1;
  523. return;
  524. }
  525. $Seen->{$Token} = 1;
  526. if (exists($Deps->{$Token})) {
  527. foreach my $Dep (sort @{$Deps->{$Token}}) {
  528. do_procdep($Dep, $Deps, $Seen, $Order, $Fail);
  529. return if $$Fail;
  530. }
  531. }
  532. push @$Order, $Token;
  533. $Seen->{$Token} = 2;
  534. }
  535. for my $Dep (sort keys %Deps) {
  536. do_procdep($Dep, \%Deps, \%Seen, \@Order, \$Fail);
  537. }
  538. return () if ($Fail);
  539. return @Order;
  540. }