-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathdeptool-bootstrap
6144 lines (5021 loc) · 200 KB
/
deptool-bootstrap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
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
355
356
357
358
359
360
361
362
363
364
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
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
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
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
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
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
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
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/perl -w
# vim: set filetype=perl: # -*- Perl -*-
#
# (c) Copyright 2004-2008, Hewlett-Packard Development Company, LP
#
# See the file named COPYING for license details
=pod
=head1 NAME
deptool - a program for helping with the software development process
=cut
use strict;
use Carp;
use Cwd qw/getcwd chdir/;
use Data::Dumper;
use English;
use Fcntl;
use File::Basename;
use File::Compare;
use File::Copy;
use File::Path;
use File::Find;
use File::stat;
use FileHandle;
use Getopt::Long;
use IPC::Open3;
use MIME::Base64;
use POSIX 'ceil';
use Pod::Usage;
use Safe;
use Sys::Hostname;
# TODO: split this file into separate pieces for the different commands or
# something, it's gotten gigantic.
BEGIN {
# One use of time in the code; shouldn't really matter, may help a little.
eval "use Time::HiRes 'time';";
if ($@) {
die $@ unless $@ =~ m!Can.t locate Time/Hires.pm in !;
}
# Debian Wheezy (7.0) dropped Digest::SHA1
eval "use Digest::SHA 'sha1_hex';";
if (!$@) {
eval 'sub hexDigest { return sha1_hex($_[0]); };';
} else {
die $@ unless $@ =~ m!Can.t locate Digest/SHA.pm in !;
# RHEL5 doesn't have Digest::SHA1 by default, only MD5
eval "use Digest::SHA1 'sha1_hex';";
if (!$@) {
eval 'sub hexDigest { return sha1_hex($_[0]); };';
} else {
die $@ unless $@ =~ m!Can.t locate Digest/SHA1.pm in !;
eval "use Digest::MD5 'md5_hex';";
die "Can not use Digest::SHA1 or Digest::MD5: $@" if $@;
eval 'sub hexDigest { return md5_hex($_[0]); };';
warn "Using Digest::MD5 instead of Digest::SHA1.
Install package or set the DEPTOOL_NOWARN_MD5 environment variable"
unless defined $ENV{DEPTOOL_NOWARN_MD5};
}
}
}
# TODO: pull should walk in the current directory before walking in the
# dependent ones since it may need additional dependencies, and/or they
# may have changed. Test case is revision in tla that switched the
# dependencies around.
# TODO: check for make file left overs in source directory (these should only
# be in build, or debug, or etc. directories). Jay at least seems to futz up
# and make crap in src every so often those breaking depbuild (or at least
# misleading it as to what has been created and what should be recreated).
# TODO: think about where to document the code to remove the .deptool.build_stamp
# file now present in Lintel/CMakeLists.txt
# TODO: add ability to have hooks for pre-commit checks, this allows
# for mechanical verification of common errors.
# TODO: figure out a way to run all of the deptool commands for
# regression testing; this may need to wait until we have alternative
# monotone directory support.
# TODO: deptool should add text to the checkin message indicating
# whether testing was performed for each commit.
# TODO: ought to separate out all the commands into sub-modules -- would speedup starting deptool,
# and would make the code easier to deal with.
sub usage {
pod2usage(-exitval => 'NOEXIT', -verbose => 0);
confess @_ if @_ > 0;
exit(0);
}
=pod
=head1 SYNOPSIS
% deptool <command> [--man] [options] [args]
init --(git|mtn|tar) [--server=<hostname>] [branch-prefix|tar-spec...]
checkout [--(git|mtn)] [--sub-branch=...] <branch-substring...>
pull # pull branch and dependencies
build [-t (and test)] [-d (debug build)] [-o (optimize build)] [-l|local]
commit [--no-tests] # commit branch after dependencies
publish [--no-tests] # publish branch and dependencies
pin <revision> # pin branch at a particular revision
code-review <revision...> # code-review a revision
review-status <revision/selector> # check the review status
help [commands|options|environment|command-name] # get help
=head1 OVERVIEW
The deptool command is designed to assist with the software
development process. In particular, it has support for getting the
version controlled copy of software via the monotone version control
system, building, committing, and publishing said software after
running appropriate tests, and easing code reviews of a collection of
changes. Since software often has multiple dependencies that all need
to be built and updated, deptool is able to handle a related set of
software via version control dependencies specified as part of the
source code. Since you may want to build software with multiple
options, deptool is able to handle additional build-type directories
which will automatically enable configuration with additional
arguments. Deptool keeps stamp files so that it can avoid rebuilding
and testing a package which has dependencies from two separate places.
In general, use of deptool consists of the following steps:
% deptool init # initial environment preparation
% deptool checkout branch-substring... # checkout sources
% cd ~/projects/project-name
% deptool build # build project-name and its dependencies
# edit the source code
% deptool commit # optional; if it's not yet ready to be public
% deptool publish # make changes visible to other people
To set up the environment, and make it easier to move between the
source and build directories, you probably want to put something like
the following in your shell startup files; unfortunately the cdopt and
cddebug bits only work under sh-based shells right now.
DEPTOOL=`(echo $HOME/build/opt*/bin/deptool) 2>/dev/null | awk '{print $1}'`
eval `$DEPTOOL getenv for-sh`
s () { eval `$DEPTOOL cdsrc $PWD` }
b () { eval `$DEPTOOL cdopt $PWD "$@"` }
d () { eval `$DEPTOOL cddebug $PWD "$@"` }
# note in bash you have to put returns after the { and before the }
Occasionally if something has been incorrectly committed, or you want
to force reconfiguration, you may need commands like:
% deptool pin <revision> # work around bad published revision
% deptool cmake clean # clean out cmake config files
% deptool cmake # rebuild the cmake configuration
% make -j $MAKE_PARALLELISM # rebuild in parallel using the deptool env
To use deptool, you need to have at least a minimal deptool.config file 'C<{
}>'. This file is described in detail in the deptool.config section. A more
normal configuration file would list the dependencies and enable parallel
testing. Such a configuration might look like the following:
{
dependencies => [qw/Lintel OtherPackage/],
parallel_test => 1,
}
=cut
my $auto_yes_to_prompts = 1;
my $quiet = 0;
my $print_manpage = 0;
my $noconfig = 0;
my $debug = 0;
$Global::last_stamp_timestamp = time();
my @original_arguments = @ARGV;
Getopt::Long::Configure(qw/pass_through no_auto_abbrev/);
GetOptions("auto-yes!" => \$auto_yes_to_prompts,
"quiet!" => \$quiet,
"noconfig!" => \$noconfig,
"projects=s" => \$Cache::projects,
"build-root=s" => \$Cache::build_root,
"operating-system=s" => \$Cache::operating_system,
"uname-m=s" => \$Cache::uname_m,
"build-debug=s" => \$Cache::build_debug,
"build-opt=s" => \$Cache::build_optimize,
"man!" => \$print_manpage,
"monotone-dir=s" => \$ENV{DEPTOOL_MTN_REPO_DIR},
"debug!" => \$debug);
=pod
=head1 OPTIONS
=over 4
=item --no-auto-yes
Do not automatically answer 'yes' to all of the prompts that are
provided if there is a sane default answer. Default is true because
in practice that's what everyone did.
=item --quiet
Print out less output by default; the output from the commands being
run will still be printed out.
=item --projects=<path>
Specify the default projects directory. Normally this would come from
the PROJECTS environment variable, which defaults to ~/projects.
=item --build-root=<path>
Specify the default root build directory. Normally this would come from
the BUILD_ROOT environment variable, which defaults to ~/build.
=item --operating-system=<string>
Specify the operating system's name. Normally this would be specified
in the BUILD_OS environment variable, which defaults to various strings depending
on the inferred operating system, e.g. debian-etch, or rhel5.
=item --uname-m=<string>
Specify the hardware machine type. Normally this would be specified
by the UNAME_M environment variable, which defaults to the output from
uname -m.
=item --build-debug=<path>
Specify the debugging build directory. Normally this would be
specified by the BUILD_DEBUG environment variable, which defaults to
$BUILD_ROOT/dbg-$BUILD_OS-$UNAME_M, or $BUILD_ROOT/debug if that directory
exists.
=item --build-opt=<path>
Specify the optimize build directory. Normally this would be
specified by the BUILD_OPT environment variable, which defaults to
$BUILD_ROOT/opt-$BUILD_OS-$UNAME_M, or $BUILD_ROOT/optimize if that
directory exists.
=item --monotone-dir=<path>
Specify the directory to search for monotone databases that include
the current project. This can be also be specified using the
MONOTONE_DIR environment variable, or failing that, the directory
defaults to $HOME/.monotone.
=item --man
Print out the complete manual page.
=back
=cut
Getopt::Long::Configure(qw/no_pass_through auto_abbrev/);
help("all") if $print_manpage;
usage("Missing argument") if @ARGV == 0;
my %commands = ('checkout' => \&depCheckout,
'co' => \&depCheckout,
'pull' => \&depPull,
'build' => \&depBuild,
# 'clear' => \&depClear,
'commit' => \&depCommit,
'publish' => \&depPublish,
# below commands don't run in all dep dirs.
'init' => \&depInit,
'tarinit' => \&depTarInit,
'cmake' => \&runCMake,
'pin' => \&setPin,
'code-review' => \&codeReview,
'review-status' => \&reviewStatus,
'mtnpull' => sub { mtnOp("pull"); },
'mtnpush' => sub { mtnOp("push"); },
'mtnsync' => sub { mtnOp("sync"); },
'mtnserve' => \&mtnServe,
'getenv' => \&getEnv,
'cdsrc' => \&cdSrc,
'cdopt' => sub { cdBuild('opt'); },
'cddebug' => sub { cdBuild('debug'); },
# get usage without error message
'-h' => \&usage,
'--help' => \&usage,
'help' => sub { help(@ARGV); },
);
=pod
=head1 COMMANDS
Further details on all commands can be found by running C<deptool
help> I<command>
=over 4
=item init [I<--server=hostname>] [I<branch-prefix>]
Initialize the system, this will create the appropriate version
control files, and will pull the initial database(s).
=item tarinit [I</path/to/tar.bz2>...] [I<http://host/path/to/tar>]
[I<http://host/path/to/latest-release>]
Initialize the system. This will optionally download and unpack the
specified source packages.
=item checkout I<branch-substring>
Select a particular branch by using branch-substring. Checkout that
branch and all of its dependencies into $PROJECTS. You can use co as
a shorter alias for this command.
=item pull
Select the current branch based on the current directory. Pull the
current branch and all it's dependencies from the server specified in
the _MTN directory.
=item build [I<-t>] [I<-d>] [I<-o>]
Build and install the current branch after building and installing its
dependencies. Select the type of build (debug or optimized) based on
the current directory or the options.
=item commit [I<--no-tests>] [I<-d>] [I<-o>]
Commit the current branch after committing its dependencies. If there
are unknown files, prompt for them to be either added or ignored. If
there are changes, perform a build before performing the commit.
Tests will be run by default before committing, but can be disabled
with --no-tests. You can also specify the build type to be run before
committing.
=item publish [I<--no-tests>]
Perform the full publishing steps; first run a commit, then build and
test, next pull and update. If no changes were made, synchronize the
updates to the central server. Alternately, restart the loop at the
build and test stage.
=item cmake [I<--clean> | I<clean> | I<cmake options...>]
Run the cmake configuration command, or clean out the cmake
configuration files. See help cmake for details on how cmake is
run. Additional cmake options can also be specified.
=item pin I<revision>
Pin the current source directory to a particular revision after
updating the current source directory to that revision.
=item code-review I<revision>...
Perform a code review of the selected revisions. This will treat the
collection of revisions as if they were a single change. Currently it
works poorly if there are renames in the changes.
=item mtnpull
Run the mtn pull operation with the appropriate options based on the
current directory.
=item mtnpush
Run the mtn push operation with the appropriate options based on the
current directory.
=item mtnsync
Run the mtn sync operation with the appropriate options based on the
current directory.
=item mtnserve [--work-dir=I</path>] [--config-from=I<base-URL>]
Start up a monotone server based on the database for the current
directory, or as a general repository based on the configuration
of some other repository.
=item getenv I<variable> I<for-{sh,csh}>
Print the selected environment information. If a variable is
specified, then just that variable will be printed. If for-sh or
for-csh is specified, then all environment variables will be printed
using the syntax of the specified shell.
=item cdsrc I<dir>
Print out the command to cd to the source directory for the specified
directory.
=item cdopt I<dir> [I<build-type>]
Print out the command to cd to the optimized build directory for the
specified directory and optional build type. Currently does not work
for csh-style shells.
=item cddebug I<dir> [I<build-type>]
Same as cdopt, except for the debug build directory.
=back
=head1 COMMAND DETAILS
=cut
# TODO: when we move to separate modules for all the packages, below should be unnecessary
@deptool::GitVC::ISA = qw/deptool/;
@deptool::MonotoneVC::ISA = qw/deptool/;
main();
sub main {
my $command = shift @ARGV;
my $fn = $commands{$command};
usage("Unrecognized command '$command'") unless defined $fn;
$|=1;
# handle case where start dir is a symlink
aliasChdir($ENV{PWD}) if defined $ENV{PWD};
&$fn;
}
# TODO: factor this out into a library so that it can be used by lots
# of things.
sub helpSection {
my ($section) = @_;
my $fn;
if ($PERL_VERSION ge v5.10) {
eval q! # checked on Debian Lenny, openSUSE 11.1
use IO::String;
use Pod::Select;
use Pod::Text::Termcap;
my $out = IO::String->new;
podselect({-sections => [$section], -output => $out }, $0);
my $to_text = new Pod::Text::Termcap;
$out->setpos(0);
$to_text->parse_from_filehandle($out);
!;
if ($@ && $@ =~ m!Can.t locate IO/String.pm in!o) {
# Write out to a temporary file.
eval q!
use Pod::Select;
use File::Temp;
use Pod::Text::Termcap;
my $fh = new File::Temp();
# Couldn't figure out how to pass just the handle to podselect;
# the idiom suggested on the File::Temp page didn't work (wrote
# to the redirect as if it was a filename)
podselect({-sections => [$section], -output => $fh->filename }, $0);
my $to_text = new Pod::Text::Termcap;
$to_text->parse_from_file($fh->filename);
!;
}
} else {
# RHEL4 (perl 5.8.5), Debian Etch (perl 5.8.8)
# -sections => ... works on Etch but not RHEL4
$fn = eval q!
my $parser = new Pod::Usage(USAGE_OPTIONS => {-verbose => 99});
$parser->select($section);
$parser->parse_from_file($0);
!;
}
die "Eval-for-docs failed: $@" if $@;
exit(0);
}
sub help {
my ($what) = @_;
$what ||= 'all';
if ($what eq 'all') {
# Might be able to get rid of the 'user contributed perl doc'
# stuff in the man page title by invoking perldoc ourselves.
pod2usage(-verbose => 2, -exitval => 0);
} elsif ($what eq 'commands') {
helpSection("COMMANDS");
} elsif (defined $commands{$what}) {
helpSection("COMMAND DETAILS/^$what(\\s.*)?\$");
} else {
usage("Unknown option to help '$what'");
}
exit(0);
}
##### Pre-declarations
sub depWalk ($$$);
##### Dependency Init
=pod
=head2 init [--(mtn|git|tar)] [vc-specific-options] [I<repository/tar specifier>...]
The init command will initialize the default setup for the specified version control system. If no
version control system is specified, it will default to git. You can specify at most one of the
different version control systems on a single command line since the behavior is enough different
that specifying multiple ones is unlikely to do the right thing.
=head3 init --git [--server=I<hostname>] [I<repository specifier>]
The init command will initialize the global configuration for git, such as the name and email
address of the committer. It will also download one or more repositories, by default the one for
contining Lintel. The repository specifiers can either be short names, such as "simpl/Lintel" in
which case the server hostname will be added or full URLs such as
ssh://[email protected]:dataseries/Lintel.git If you specify a short name, then the default server
will be assumed to be git-pa1.hpl.hp.com, which is only accessible from the HP intranet.
Init will make a full mirror of the remote repositories into $HOME/.git/I<shortname>. Right now it
assumes that the repositories are readable through the git protocol and writeable through the ssh
protocol, and it will set up the configuration to operate in that mode.
In order to write to a particular project you have to perform two steps. First you have to send
your ssh key (e.g. $HOME/.ssh/id_rsa.pub; in the format of an authorized_keys file) to the
appropriate administrator. Second you need to get permission to write to the project. It is
slightly preferrable to have only one ssh key because it leads to additional work for the
administrator to have multiple ones, but the latter can work. The expected format of a key is the
openssh authorized_keys format, i.e. a line starting with ssh-rsa (or dsa), then a long series of
alphanumeric characters and then a name for the key (usually an email address).
=head3 init --mtn [--server=I<hostname>] [I<repository specifier>]
This command will also populate the initial database for checkouts, by default, the one that
would contain Lintel since deptool is in Lintel. By default it assumes that usi.hpl.hp.com is the
central version control server, but this can be overridden with the --server option. It will use
ssd as the default branch prefix. If you do not have the environment variables set up by using the
getenv command, deptool will ask a number of questions about where the files should be placed. It
is usually safe to run the init command with the -y option so that these questions will be
automatically answered.
After you have run init, you will have a key file. To be able to sync to a particular server, you
need to send the public key to the monotone administrator for that server. You can get the key
file by running mtn pubkey I<email-address>, the same address you used when running deptool init.
For HP Labs, the administrator of usi.hpl.hp.com is [email protected].
B<Warning:> If you already have a key set up on one machine, you should just copy your keys
directory to the new machine rather than creating a new key. Monotone can get confused by multiple
keys with the same email address.
=head3 init --tar [I<download-specification>]
The tarinit command will initialize the software packages based on tar files. It has three
possible types of download specifications, listed below. If no arguments are specified, then it
defaults to http://tesla.hpl.hp.com/opensource/latest-release; which will download the open-source
packages available on tesla and unpack then.
=over 4
=item I</path/to/file.tar.bz2>
This specifies that deptool should unpack the specified tar file into the projects directory. The
tar file will normally unpack into a directory names I<package>-I<date>, in which case the -I<date>
portion will be removed. If there is already a I<package> available in the projects directory, it
will only be replaced if it has a different release date than the file.tar.bz2 one.
=item I<http://host/path/to/tar>
This specifies that deptool should first download the tar file into the projects directory and then
unpack it as if the downloaded file had been specified to tarinit. Deptool will use wget to
download the file. If the tar file has already been downloaded, it will just be used.
=item I<http://host/path/to/latest-release>
This specifies that deptool should download the specified text file, and should then download all
of the packages specified in the latest-release file.
=back
=cut
sub depInit {
my %mode;
my $server;
Getopt::Long::Configure(qw/pass_through no_auto_abbrev/);
my $result = GetOptions("server=s" => \$server,
"mtn!" => \$mode{mtn},
"git!" => \$mode{git},
"tar!" => \$mode{tar});
usage("Unknown error") unless $result;
Getopt::Long::Configure(qw/no_pass_through auto_abbrev/);
my $count = 0;
map { ++$count if $_; } values %mode;
if ($count == 0) {
$mode{git} = 1;
} elsif ($count > 1) {
die "You must specify at most one of --mtn, --git, or --tar";
}
$ENV{MTN_SERVER} = $server if defined $server;
$ENV{DEPTOOL_GIT_SERVER} = $server if defined $server;
depInitMtn($server) if $mode{mtn};
deptool::GitVC::init($server) if $mode{git};
deptool::TarVC::init() if $mode{tar};
}
sub checkForBinary {
my ($binary) = @_;
my @paths = split(/:/, $ENV{PATH});
foreach my $path (@paths) {
return 1 if -x "$path/$binary";
}
return 0;
}
sub depInitMtn {
die "Unable to find mtn binary in path; you should repair this.
Usually yum install monotone or apt-get install monotone will work.
http://usi.hpl.hp.com/twiki/pub/USI/MonotoneVersionControl/mtn-static
is a static linux binary, alternately, you can get something from
the monotone web site: http://www.monotone.ca/"
unless checkForBinary("mtn");
my $server = mtnServer();
my $email;
my @branch_prefixes = @ARGV;
if (@branch_prefixes == 0) {
print "Assuming default branch prefix ssd\n";
@branch_prefixes = ("ssd");
}
initMtnDir();
initMtnKeysRc();
initMtnDbs($server, \@branch_prefixes);
}
sub initMtnDir {
my $monotone_dir = mtnRepoDir();
if (! -d $monotone_dir) {
userPrompt("Create monotone directory $monotone_dir");
mkdir($monotone_dir, 0755) or die "Unable to mkdir $monotone_dir: $!";
}
}
sub initMtnKeysRc {
my $monotone_dir = mtnRepoDir();
if (! -d "$monotone_dir/keys") {
print <<EOF;
The key will be generated with an empty password. You can change it
by running mtn chkeypass or mtn passphrase depending on your monotone
version.
Run 'deptool help publish' to learn where you have to send your public key
so tha tyou can publish.
WARNING: if you already have a monotone key, you should ctrl-c now, and copy
WARNING: your key file over. Monotone deals poorly with multiple keys with
WARNING: the same e-mail address.
EOF
print "Enter e-mail address for identifying monotone key: ";
my $email = <STDIN>;
chomp $email;
die "Unexpected email format '$email'" unless $email =~ /^\S+\@\S+\.\w+$/o;
runCommand("yes default-password | mtn genkey $email");
print <<"END";
Key generated with password 'default-password'; can be changed with
mtn chkeypass or mtn passphrase depending on your monotone version
END
chmod(0700, "$monotone_dir/keys");
if (-f "$monotone_dir/monotonerc") {
print <<"EOF";
You probably want to add:
function get_passphrase(keypair_id)
return "default-password"
end
To your $monotone_dir/monotonerc file
EOF
} else {
userPrompt("Create default monotonerc?");
my $mtnrc = new FileHandle(">$monotone_dir/monotonerc")
or die "can't write $monotone_dir/monotonerc: $!";
print $mtnrc <<"EOF";
-- Change with mtn chkeypass or mtn passphrase, then change here.
function get_passphrase(keypair_id)
return "default-password"
end
-- Next three are for letting you run your own server
function get_netsync_read_permitted (collection, identity)
return true
end
function get_netsync_write_permitted (identity)
if (identity == "$email") then return true end
return false
end
function get_netsync_anonymous_read_permitted (collection)
return true
end
-- performance optimization
function get_vcache_size()
return 512*1024*1024
end
EOF
chmod(0700, "$monotone_dir/monotonerc");
}
}
}
sub initMtnDbs {
my($server, $branch_prefixes) = @_;
my $monotone_dir = mtnRepoDir();
foreach my $branch_prefix (@$branch_prefixes) {
my $db = $branch_prefix;
$db =~ s/\W.*$//o;
die "Invalid branch prefix $branch_prefix, should start with normal chars"
if $db eq '';
unless (-f "$monotone_dir/${db}.db") {
userPrompt("create local repository $monotone_dir/${db}.db");
runCommand("mtn -d $monotone_dir/${db}.db db init");
}
userPrompt("pull $branch_prefix* from $server (can take a while)");
my $qm = quotemeta $branch_prefix;
runCommand("mtn -d $monotone_dir/${db}.db pull $server $qm\\*");
}
}
=pod
=head2 tarinit [--no-cache] [I<download-specification>]
Deprecated form of init --tar; will be removed after 2012-12-31.
=cut
sub depTarInit {
warn "tarinit is deprecated, and will be removed after 2012-12-31,
it has been replaced by init --tar for consistency with the other forms
of init";
deptool::TarVC::init();
}
##### Dependency Checkout
=pod
=head2 checkout [--(git|mtn)] [--sub-branch=...] I<branch-substring>...
The checkout command, which can be abbreviated as co, checks out a branch and its dependencies. It
will find the appropriate branch by selecting from all the branches in the repositories found under
~/.git, and the databases found as ~/.monotone/*.db. It will then attempt to select the most
appropriate matching branch. If there is a conflict between the git and the monotone options, it
will default to the git option. This process will repeat for all dependencies of the package you
selected, for example, if you checked out DataSeries, checkout would checkout Lintel automatically.
If a sub-branch is specified, then deptool will add that sub-branch to all branches if it exists.
If it doesn't exist, deptool will check out the main branch. For example if you check out
--sub-branch=feature/parallel DataSeries, deptool will checkout DataSeries/feature/parallel, and
Lintel/feature/parallel if it exists. You can also specify required sub-branches in the branch
strings, e.g. checkout --git Lintel/feature/parallel. Error checking for nonsensical combinations
of checkouts is not well implemented.
=head3 checkout --git
Deptool will first find all of the available repositories and branches under ~/.git. It will
construct long branch names that combine the repository name with the branch name, treating the
master branch as empty. E.g. if you have a repository simpl/Lintel with branches master,
feature/goodness, and personal/eric; you will get a list simpl/Lintel, simpl/Lintel/master,
simpl/Lintel/feature/goodness, simpl/Lintel/personal/eric. It will then identify all branches that
match the specified substring, and select the shortest one if it is a unique prefix of the others
and otherwise report an error.
Deptool will select the directory name based on the last part of the repository name, e.g. if the
repository is named simpl/something/Project, the directory name for the checkout will be
~/projects/Project. The root for checkouts can be changed by setting $PROJECTS.
=head3 checkout --mtn
Deptool will automatically select the branch that matches with the least length so it will ignore
sub-branches that may be present. For example, if you ran checkout Data, it would select the
ssd.hpl.hp.com/DataSeries branch and put the checkout in ~/projects/DataSeries (by default,
changeable by setting $PROJECTS).
Deptool will choose the directory name based on the branch name after stripping out the initial
uniquifier. This is based on the assumption that branches will be named as
I<uniquifier>/I<branch-name>/I<sub-branch>.
If multiple branches match and they do not share a common prefix, deptool will abort the checkout.
=head2 co
See help checkout
=cut
# TODO: general consensus between Joe and Eric is that if you do a checkout of a related set of
# sub-branches that all the sub branches should show up in their primary directory. e.g. 'deptool
# co --sub-branch=testing KeyValue' should pick out all the testing sub-branches in the ~5
# recursive dependencies of KeyValue, and put them in directories named KeyValue, SimReal, gflib,
# etc. The rationale is that in this situation you probably are using separate PROJECTS and
# BUILD_* directories.
#
# However, you can also want a separate sub-directory for a related version of a package, e.g. if
# you are working on the parallel mode for DataSeries, or support for git in deptool, you can want
# to keep that separate from the existing primary branch, in which case having the checkout in
# Lintel_git, which allows you to work in a separate directory or branch, but still get it
# installed into the common location so that it can be tested.
#
# Support for the former mode is pretty complete since you can set PROJECTS and BUILD_OPT, and the
# right thing just happens; support for the latter is manual. If we are going to support common
# use of transient branches for git, then we probably want to support commands like make-subbranch,
# propagate (to a further sub-branch or to a separate branch), and delete-subbranch; or some
# equivalent set of commands. For now the conclusion is that we need to gain some experience
# working with git to get better feel for how working with branches should operate, i.e. just do it
# manually and eventually encode a workflow into deptool. We may want to have related sub-branches
# like DataSeries_parallel_sort and _parallel_analysis for which propagate up and down the tree is
# simplified.
sub depCheckout {
my ($mtn, $git, $sub_branch);
my $result = GetOptions("mtn" => \$mtn, "git" => \$git, "sub-branch=s" => \$sub_branch);
usage("Unknown error") unless $result;
usage("No branch-substrings specified") unless @ARGV > 0;
unless ($mtn || $git) {
$mtn = $git = 1;
}
my @branches;
my %branch_to_info;
depCheckoutGetMtnBranches(\@branches, \%branch_to_info) if $mtn;
depCheckoutGetGitBranches(\@branches, \%branch_to_info) if $git;
my @branch_regexes = (@ARGV);
my %did_regex;
while (@branch_regexes > 0) {
my $branch_regex = shift @branch_regexes;
next if $did_regex{$branch_regex};
$did_regex{$branch_regex} = 1;
my $branch = depCheckoutSelectBranch($branch_regex, \@branches, \%branch_to_info);
if (defined $sub_branch) {
my $tmp = eval { depCheckoutSelectBranch("$branch_regex/$sub_branch", \@branches,
\%branch_to_info); };
if (!$@ && defined $tmp) {
$branch = $tmp;
} else {
print "WARNING: no sub-branch found matching $branch_regex/$sub_branch\n";
}
}
my $info = $branch_to_info{$branch};
my $fn = $info->{fn};
my $path = &$fn($branch, $info);
my $config = readConfig($path);
if (defined $config->{dependencies} && @{$config->{dependencies}} > 0) {
print "Adding VC dependencies: ", join(" ", @{$config->{dependencies}}), "\n";
push(@branch_regexes, @{$config->{dependencies}});
}
}
}
sub depCheckoutSelectBranch {
my ($branch_regex, $branches, $branch_to_info) = @_;
$branch_regex =~ s/;\w+//o;
$branch_regex = "/$branch_regex" unless $branch_regex =~ m!^/!o;
my @possibles = grep(m/$branch_regex/, @$branches);
die "No branches found matching $branch_regex"
unless @possibles > 0;
@possibles = sort { length $a <=> length $b } @possibles;
my %vcs;
map { my $info = $branch_to_info->{$_} || die "?"; $vcs{$info->{vc}} = 1; } @possibles;
if ($vcs{git} && $vcs{mtn}) { # prefer git to mtn.
print "Found both git and monotone dbs. Pruning out monotone dbs for
$branch_regex, use --mtn to force usage\n";
@possibles = grep($branch_to_info->{$_}->{vc} ne 'mtn', @possibles);
}
my $branch = shift @possibles;
foreach $_ (@possibles) {
die "multiple non-prefixed matches for $branch_regex:
$branch $_
You need to provide more specificity in the dependencies in deptool.config\n "
unless /^$branch/;
}
return $branch;
}
sub depCheckoutGetMtnBranches {
my ($branches, $branch_to_info) = @_;
my @dbs = glob(mtnRepoDir() . "/*.db");
return if @dbs == 0; # Just be silent for case where someone is git (or svn) only.
@dbs = sort @dbs;
foreach my $db (@dbs) {
my $tmp = new FileHandle("mtn -d $db list branches |");
my @tmp = <$tmp>;
close($tmp);
grep(chomp, @tmp);
push(@$branches, @tmp);
map {
warn "duplicate db ($db, $branch_to_info->{$_}->{mtn_db}) for branch $_
will use latter."
if defined $branch_to_info->{$_} && defined $branch_to_info->{$_}->{mtn_db};
$branch_to_info->{$_} = { mtn_db => $db, fn => \&depCheckoutMonotoneCheckout,
vc => 'mtn' };
} @tmp;
}
}
sub depCheckoutGetGitBranches {
my ($branches, $branch_to_info) = @_;
my $git_repo_dir = gitRepoDir();
my @repo_dirs;
find(sub {
return unless -d "$_/objects";
# branches doesn't always exist
foreach my $subdir (qw!hooks info objects objects/pack objects/info refs!) {
return unless -d "$File::Find::name/$subdir";
}
foreach my $subfile (qw!config description HEAD!) {
return unless -f "$File::Find::name/$subfile";
}
push(@repo_dirs, $File::Find::name);
$File::Find::prune = 1;
}, "$git_repo_dir/"); # extra / to force lookup through symlink
# in case working dir is local but git repo is on a shared filesystem.
print "Found repo dirs: @repo_dirs\n";
foreach my $repo_dir (@repo_dirs) {
my $prefix = $repo_dir;
die "? $prefix $git_repo_dir"
unless substr($prefix, 0, length $git_repo_dir) eq $git_repo_dir;
substr($prefix, 0, length $git_repo_dir) = '';
print "$prefix:\n";
my $qm = quotemeta $repo_dir;
my $fh = new FileHandle("git --git-dir=$qm branch |")
|| die "Can't run git branch: $!";
my @sub_branch;
while (<$fh>) {
die "? $_ " unless /^\*? +(\S+)$/o;
print " $1\n";
push (@sub_branch, $1);
push (@sub_branch, "") if $1 eq 'master';
}
close($fh);
die "git branch failed: $?" unless $? == 0;
foreach my $sub_branch (@sub_branch) {
my $branch = $prefix;
$branch .= "/$sub_branch" unless $sub_branch eq '';
die "? $branch" if defined $branch_to_info->{$branch};
$branch_to_info->{$branch} = { git_dir => $repo_dir, fn => \&depCheckoutGitCheckout,
co_path => $prefix, vc => 'git',
sub_branch => $sub_branch };
push(@$branches, $branch);
}
}
}
sub depCheckoutMonotoneCheckout {
my ($branch, $info) = @_;
my $db = $info->{mtn_db};
my $dir = $branch;
$dir =~ s!^\w+(\.\w+)*/!!o; # strip uniqueifier we put on
my $path = projects() . "/$dir";
print << "END";
Selected monotone branch $branch from database $db
Checking out into: $path