-
Notifications
You must be signed in to change notification settings - Fork 41
/
ch03.txt
1106 lines (743 loc) · 39.2 KB
/
ch03.txt
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
// kate: font Liberation Sans; font-size 16; syntax None; bom off; indent-mode none;
.bookmark zproject
+ The zproject Tool
In the last chapter I explained a lot of rules and conventions for writing a Scalable C project. It looks like a lot to remember. The good news is that if we are consistent, it pays off. For example if we always put our sources into {{src}} and our headers into {{include}}, it is easier to reuse build scripts between projects.
Speaking of build scripts...
++ Problem: Infinite Sucking Pits of Darkness
I'm speaking of Makefiles. Wikipedia tells us Make was invented by Bell Labs in 1976. //Wikipedia lies!// The real truth is that Makefiles are digital demon devisements from the darkest depths of Dis. Some say Bell Labs was the portal through which they clawed their way into our innocent world. We still don't know their true purpose. All we know is, they are eternal and cannot be killed. And we know the dying sound our soul makes as it leaves our body.
Makefiles spawned an entire legion of descendant demons called the {{autotools}}. There are said to be ancient scrolls that provide the incantations to tame {{autotools}} demons. A piece of one landed on my desk. This is what it said:
[[code]]
# Resolve issue #355, "client wants to replace me"
AC_ARG_WITH([pkgconfigdir],
AS_HELP_STRING([--with-pkgconfigdir=PATH],
[Path to the pkgconfig directory [[LIBDIR/pkgconfig]]]),
[pkgconfigdir="$withval"],
[pkgconfigdir='${libdir}/pkgconfig'])
AC_SUBST([pkgconfigdir])
[[/code]]
Makefiles and build systems that laughing call themselves "makefile generators" as if that made things simpler are inevitable. There is no escape to an alternate universe, if you are writing C code. Oh, please someone tell me how "you need make to reduce build times." I need a good laugh while Travis CI trundles through a fifteen-minute build, every time I push a commit to GitHub.
That being said...
//Solution: make it someone else's problem.//
Happily, this solution actually worked. It comes as close to killing makefiles as possible, after about 25 years of research. As often, the brilliance and genius comes from the collective mind.
What my team, at iMatix did, many years ago, was to build [https://github.com/imatix/gsl a way to generate code] from high level models. We used this a lot and got good at it. Our GSL language makes it possible to develop DSLs (domain specific languages) quickly, and then build backends that turn these DSLs into code. Not to be confused with the GNU Scientific Library (also known as GSL).
What the ZeroMQ community did, over about two years, was build [https://github.com/zeromq/zproject/graphs/contributors a DSL for packaging], and write dozens of backends for it. This tool is called {{zproject}}, and it is what I'll explain in this chapter.
Remember this lesson:
> Never give up. If you wait long enough, the ZeroMQ community may solve your problem for you.
++ Problem: I don't got zproject
//Solution: get it from GitHub.//
```
# Install gsl first
git clone https://github.com/imatix/gsl
cd gsl/src
make -j 4 && sudo make install
cd ..
# Install zproject
git clone https://github.com/zeromq/zproject
cd zproject
./autogen.sh && ./configure
make && make install
```
Remember this lesson:
> Once you go master, you never go back.
++ Problem: we need an example
The fastest way to learn any new tool is by example. Let's make a minimal project by hand, then apply magic. Our project is called "Global Domination." Right now version 0.1 is small and modest. It is a skeleton project that fits the rules of Chapter 2, and does nothing more.
//Solution: get the code from GitHub.//
```
git clone https://github.com/scalable-c/globdom
cd globdom
git reset --hard version-0.1
cat README.md
```
The minimal project contains one empty class, and supporting files:
* {{LICENSE}} -- MPLv2 license text
* {{README.md}} -- this file
* {{include/globaldom.h}} -- project public header
* {{include/gdom_server.h}} -- Global Domination server API
* {{src/gdom_server.c}} -- Global Domination server
* {{src/gdom_classes.h}} -- project private header
* {{src/gdom_selftest.c}} -- project selftest tool
* {{build.sh}} -- build and test Global Domination
* {{.gitignore}} -- tell git what files to ignore
To build and test GlobDom 0.1, run {{build.sh}}.
Remember this lesson:
> Learn the basics of Bash, it will save your life many times.
++ Problem: people expect "./configure && make"
It is possible, and I've done this on real projects, to work without Makefiles. You compile stuff, chuck it into libraries, and link your executables. Yet it bounces off the wall of expectations. Also, the endless weirdness of the real world. Any real build process gets complex. And so people turn to Makefile generators, much like the victim of a street mugging turning to Somali warlords for help.
Let me be frank, for a change. I do not like the GNU build system, even after mastering it. It uses a flat yet vast macro language to generate Makefiles by sheer brute force. It may be powerful, yet so are the technicals those Somali warlords like to drive to work. The only good thing about autotools is that if (and this is a large if) you can master it, or find someone who's done this, then it is solid.
Happily we figured out how to use autotools' considerable power from a position of blissful ignorance. Let me show you how.
//Solution: tell zproject what your project looks like.//
First, create a file {{project.xml}} in the globdom root directory, like this:
```
<project
name = "Global Domination"
script = "zproject.gsl"
prefix = "gdom" >
<use project = "czmq" />
<class name = "gdom_server" />
</project>
```
This should be self-explanatory. If you've heard bad things about XML, or been traumatized by it in the past, my sympathies. Give some people a set of hammers, and they think they're rock star drummers. XML is not a programming language. It is however great for writing models to generate code from. You'll learn the fun and profit in this.
Second, run the {{gsl}} command to build the project model:
```
globdom> gsl project.xml
GSL/4.1c Copyright (c) 1996-2016 iMatix Corporation
gsl/4 I: Processing project.xml...
gsl/4 M: Building GNU build system (autotools)
gsl/4 M: Building CMake build system (cmake)
```
And now, the "configure/make" thing works. Run "./autogen.sh" first, as that produces the configure script:
```
./autogen.sh
./configure
make -j 4
make check
```
Remember this lesson:
> If everyone expects cake, give them cake.
++ Problem: sorry, I meant "cmake..."
No problems. As you saw, zproject supports both CMake and the GNU build system. So:
```
cmake .
make -j 4
make test
```
//Solution: zproject targets both autotools and CMake.//
If autotools are the Somali warlords of build systems, then CMake is the Texas politician who promises wealth and power. CMake still wants your soul, yet it is has much more charisma. "I can take care of Visual Studio for you!" it says, smiling, playing off our inner fears.
My main gripe with CMake is that it is just a better build scripting system. It doesn't change the basic fact that //I don't want to write build scripts because they're always doing the same bloody work!//
//Solution: don't script when you can model.//
With zproject we don't write scripts. Instead we document our projects as abstract XML models. Then, we can run arbitrary backends on this model, each doing what it must. It is a profound and valuable shift.
Just to finish my complaints about CMake, it lacks a "clean" command. And since it leaves trash lying all over the place, and since that trash really gets in the way sometimes, this is bad. The solution people use is to [http://stackoverflow.com/questions/9680420/looking-for-a-cmake-clean-command-to-clear-up-cmake-output build in a temporary directory]. It isn't great. Thus, zproject generates a `make distclean` target for cmake similar to the autotools one which cleans up all cmake generated files nicely. It isn't great either but better than no cleaning at all.
Still, since we get CMake support for free, why complain.
Remember this lesson:
> Even if you hate a particular build system, someone out there is addicted to it.
++ Problem: what do I add to git?
Ah, our directory is now a mess of different files. We have some made by hand, some produced by autotools, some by CMake, and some by the compiler and linker.
We cannot add //everything// to git, because many of these files change every time we compile, and do not belong in the repository. Yet we need the basic build scripts in git, otherwise no-one will know how to use our code.
//Solution: put the output of zproject in git.//
Let's rewind. First, save project.xml. Then reset the clock using {{git clean}}. Then run zproject again:
```
mv project.xml ..
git clean -d -f -x
mv ../project.xml .
gsl project.xml
```
Let's look at what zproject actually generated for us. Run {{git status}} to see all new and changed files:
```
globdom> git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: src/gdom_classes.h
modified: src/gdom_selftest.c
Untracked files:
(use "git add <file>..." to include in what will be committed)
CMakeLists.txt
Findczmq.cmake
Findlibzmq.cmake
Makefile.am
autogen.sh
configure.ac
doc/
include/gdom_library.h
include/global_domination.h
project.xml
src/.valgrind.supp
src/Makemodule.am
src/libgdom.pc.in
```
Two of our hand-written files got smashed by generated versions. That's intentional. We won't modify these by hand ever again, as they track the project. Then we got a lot of new files, for autotools and for CMake. And then we got a project header called {{include/global_domination.h}}. Cute! But useless! Let's tolerate that for now, and fix it later.
Add these files to git and commit:
```
git add .
git commit -m "Problem: git repo doesn't contain build scripts
Solution: add everything that zproject generates"
```
Now run {{./autogen.sh && ./configure && make check}} again. You should see lots of output, ending like this:
```
/bin/bash ./libtool --mode=execute ./src/gdom_selftest
Running global domination selftests...
* gdom_server: OK
Tests passed OK
...
```
Remember this lesson:
> When using {{git clean}}, save any hand-written files first.
++ Problem: {{git status}} shows lots of junk
Building your project will produce lots of files and directories scattered around. Running {{git clean}} too often is a bad idea, as it will wipe any new files you've written.
//Solution: use a more complete .gitignore file.//
It gets tedious to write a complete .gitignore file. Happily we have a tool whose intention is precisely to do the tedious things involved in building Scalable C projects. Here is how we get a complete .gitignore file:
```
rm .gitignore
gsl project.xml
```
You'll see this in the output:
```
gsl/4 M: Generating initial .gitignore file
```
Now type {{git status}} and the image comes in focus:
```
git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: .gitignore
no changes added to commit (use "git add" and/or "git commit -a")
```
Let's add this and commit:
```
git add .
git commit -m "Problem: .gitignore needs more beef
Solution: delete and regenerate via zproject"
```
Remember this lesson:
> zproject generates .gitignore for us, if we don't have it.
++ Problem: I need to make a new class
Global domination is well on the way! Now we'd like to add a client class. We'll offer two APIs. One is for those who wish to run the GlobDom server in their code. The second is for those who want to access it over the network. So let's make a client class. Like our server class, it'll do nothing, yet. First draw the outline, then fill it in.
//Solution: add new classes to project.xml.//
Here is how we define the client class in {{project.xml}}:
```
<project
...
<class name = "gdom_client" />
</project>
```
Then we run {{gsl project.xml}} again and see what {{git status}} gives us. There's a few changes to build scripts, then two new files:
```
Untracked files:
(use "git add <file>..." to include in what will be committed)
include/gdom_client.h
src/gdom_client.c
```
Take a quick look at these generated files. The {{include/gdom_client.h}} header defines a bare minimum for a typical class:
[[code]]
/* =========================================================================
gdom_client - class description
Copyright (c) the Authors
=========================================================================
*/
#ifndef GDOM_CLIENT_H_INCLUDED
#define GDOM_CLIENT_H_INCLUDED
#ifdef __cplusplus
extern "C" {
#endif
// @interface
// Create a new gdom_client
GDOM_EXPORT gdom_client_t *
gdom_client_new (void);
// Destroy the gdom_client
GDOM_EXPORT void
gdom_client_destroy (gdom_client_t **self_p);
// Print properties of object
GDOM_EXPORT void
gdom_client_print (gdom_client_t *self);
// Self test of this class
GDOM_EXPORT void
gdom_client_test (bool verbose);
// @end
#ifdef __cplusplus
}
#endif
#endif
[[/code]]
And {{src/gdom_client.c}} implements these three methods with little more than air and used chewing gum. Let's do the usual sanity check:
```
make check
```
Remember this lesson:
> zproject generates classes for you if you need them.
++ Problem: generated sources don't have a license
These two new files need a license blurb at their start. Now, we could add the license blurb by hand. zproject does not touch the class header or source once those files exist. Plus, there are other files that zproject gives us, like gdom_selftest.c, which also need a license blurb.
//Solution: specify a blurb in our project model.//
We'll put the blurb into a separate XML file so it doesn't clutter our project model. Create a file {{license.xml}} with this content:
[[code]]
<license>
Copyright (c) the Contributors as noted in the AUTHORS file.
This file is part of Global Domination. Resistance is useless.
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
</license>
[[/code]]
And now include that into your {{project.xml}} thus:
[[code]]
<project ...>
<include filename = "license.xml" />
...
</project>
[[/code]]
Now, let's check that this works:
```
rm src/gdom_client.c
rm include/gdom_client.h
gsl project.xml
```
If {{gsl}} complains, fix the XML syntax and try again. Now look at the {{src/gdom_client.c}} file. It should start like this:
[[code]]
/* ===================================================================
gdom_client - class description
Copyright (c) the Contributors as noted in the AUTHORS file.
This file is part of Global Domination. Resistance is useless.
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
===================================================================
[[/code]]
Take a peek at {{src/gdom_selftest.c}} and you will see the same blurb.
Remember this lesson:
> Stop deleting src/gdom_client.c. From now you, you edit this by hand.
++ Problem: the client wants to run a server
As luck would have it, Global Domination's sales team already has a potential customer, who's offering good money for a server. You just need a demo, and the sooner the better.
//Solution: write a server program.//
What the server program does is create a {{gdom_server}} instance, print an optimistic message, wait for Ctrl-C, and then exit. How hard can it be?
Let's start by adding a 'main' element to {{project.xml}}:
```
<main name = "gdomd">Global Domination Demon</main>
```
Now regenerate the project with the usual {{gsl project.xml}}. You'll notice that zproject tells you:
```
gsl/4 M: Generating skeleton for src/gdomd.c
```
The skeleton {{src/gdomd.c}} doesn't do anything useful, or even pretend to. Let's open it up in an editor and add some body to it:
[[code]]
...
// Insert main code here
if (verbose)
zsys_info ("Welcome to Global Domination v0.1");
zsys_info ("starting Global Domination server...");
gdom_server_t *server = gdom_server_new ();
assert (server);
while (!zsys_interrupted) {
sleep (1);
}
zsys_info ("terminating Global Domination server...");
gdom_server_destroy (&server);
return 0;
}
[[/code]]
The {{zsys_info}} method is one of a bunch that do system logging. The others are:
* {{zsys_error}} - log error condition - highest priority.
* {{zsys_warning}} - log warning condition - high priority.
* {{zsys_notice}} - log normal condition - normal priority.
* {{zsys_info}} - log information message - low priority.
Run {{man zsys}} to see some of the other methods in the CZMQ {{zsys}} class.
Let's build and test our new server:
```
make
src/gdomd -v
```
It should say:
```
gdomd - Global Domination Demon
I: 16-01-20 18:11:07 Welcome to Global Domination v0.1
I: 16-01-20 18:11:07 starting Global Domination server...
```
Remember this lesson:
> Better a hundred small steps than one giant leap.
++ Problem: update the version number
After such a lot of work we should release a new version. We defined the version number in our public header file, {{include/globaldom.h}}. It should be simple to bump it:
* Edit {{include/globaldom.h}} to say {{#define GLOBDOM_VERSION_MINOR 2}}
* Print the version number from the header:
[[code]]
zsys_info ("Welcome to Global Domination v%d.%d",
GLOBDOM_VERSION_MAJOR, GLOBDOM_VERSION_MINOR);
[[/code]]
Run {{make}} and... we get compile errors like this:
```
gdomd.c: In function ‘main’:
gdomd.c:46:21: error: ‘GLOBDOM_VERSION_MAJOR’ undeclared
```
The reason is that our project header files are confused. We have a mix of hand-written files (the original {{include/globaldom.h}}) and generated files doing the same thing. Take a look at {{include/gdom_library.h}} and you'll see it does the same (and more) as we did by hand.
//Solution: define the version and header in our project model.//
First, let's fix the header mismatch. Add this {{header}} attribute to the project item:
```
<project
name = "Global Domination"
script = "zproject.gsl"
prefix = "gdom"
header = "globaldom.h">
...
```
Now delete these two files:
```
rm include/global_domination.h
rm include/globaldom.h
```
The first is junk and has to go. The second is our hand-written project header. When we delete it, and then build the project again, zproject gives us a new skeleton header. It does nothing except pull in {{gdom_library.h}}, which has all of our public API. This split lets us add hand-written code (to {{globaldom.h}}) while also generating the public API (in {{gdom_library.h}}).
It sounds tricky yet once things are working, you can more or less forget about it. Rebuild the project with {{gsl project.xml}} as usual. You should see this output:
```
gsl/4 M: Generating skeleton for include/globaldom.h
```
Now look at the version macros in {{include/gdom_library.h}}:
[[code]]
#define GDOM_VERSION_MAJOR 0
#define GDOM_VERSION_MINOR 0
#define GDOM_VERSION_PATCH 0
[[/code]]
To define a version in the project, we add a 'version' item like this:
[[code]]
<version major = "0" minor = "2" />
[[/code]]
Run {{gsl project.xml}} again and see how {{include/gdom_library.h}} changes. Now we can fix our main program, and make the project:
[[code]]
zsys_info ("Welcome to Global Domination v%d.%d",
GDOM_VERSION_MAJOR, GDOM_VERSION_MINOR);
[[/code]]
```
make
src/gdomd -v
```
Remember this lesson:
> Simple is hard.
++ Problem: someone's patch broke my code
Congratulations, you got a contributor! It is a precious thing, someone joining your project. To criticize their work is clumsy, and foolish. Yet most open source projects treat patches like lollipops offered by strangers in a park. I've already discussed why optimistic merging is sane and effective.
Yet, it's nice to give people rapid feedback on their work. It might take us a day to see someone's patch and spot a mistake. We have computers to do this kind of stuff.
//Solution: enable Travis CI on your project.//
"CI" is short for "build everything and run make check, and see if it works." I'm sure there are other CI systems yet Travis wins for being simple, fast, and reliable.
To enable Travis, add this line to your project.xml:
[[code]]
<target name = "travis" />
[[/code]]
Run {{gsl project.xml}} again and you'll see this output:
[[code]]
gsl/4 I: Processing project.xml...
gsl/4 M: Building GNU build system (autotools)
gsl/4 M: Building CMake build system (cmake)
gsl/4 M: Building Travis CI scripts (travis)
gsl/4 M: Generating skeleton .travis.yml script
[[/code]]
In all cases, zproject builds the {{autotools}} and {{cmake}} targets. We've now added one more target, {{travis}}. The {{.travis.yml}} script is where we tell Travis what to do.
Do {{git status}} and you'll see two new files:
[[code]]
Untracked files:
.travis.yml
ci_build.sh
[[/code]]
The ci_build.sh file is always generated, whereas we will modify .travis.yml by hand, as we decide to expand our test cover. Here is what the skeleton Travis script looks like:
[[code]]
# Travis CI script
language: c
os:
- linux
sudo: false
env:
- BUILD_TYPE=default
#- BUILD_TYPE=android
#- BUILD_TYPE=check-py
#- BUILD_TYPE=cmake
addons:
apt:
packages:
- valgrind
before_install:
- if [ $TRAVIS_OS_NAME == "osx" ] ; then stuff
# Hand off to generated script for each BUILD_TYPE
script: ./ci_build.sh
[[/code]]
You can, if this is your kind of soup, go and learn Travis' scripting language.
* Go to travis-ci.org (not .com!).
* Click "Log in with GitHub" at the top right.
* Authorize Travis for your organizations.
* When you return to Travis, click your user icon at the top right.
* Click "Sync account" so Travis knows what repositories you have.
* Find your Global Domination project.
* Click the toggle to enable Travis.
Nothing happens until you push the {{.travis.yml}} file to your repository. So:
```
git add .
git commit -m "Problem: someone's patch broke my code
Solution: enable Travis CI"
git push origin master
```
Now Travis should start building and testing your project. Every time you push commits, it will run the .travis.yml script and report any errors.
Remember this lesson:
> Travis is there to help you, not add more stress.
++ Problem: Travis isn't building my project :(
The first time you try to use Travis it's not obvious. Just enabling builds on your repo does not start a build. So if you carefully pushed your new .travis.yml file, and then enabled builds, nothing happened.
You need to push a commit before Travis wakes up. You don't need to change anything. Git lets you create empty commits.
//Solution: push an empty commit.//
Run these commands:
```
git commit --allow-empty -m "Problem: Travis isn't building my project
Solution: push an empty commit"
git push origin master
```
Remember this lesson:
> With enough empty commits you can draw pictures on your GitHub profile.
++ Problem: my client is using Windows
Consider this an opportunity, not a problem. "We need support for Visual Studio 2010," says your client. "Hmm, that could take a week or more. Is that OK?" you reply. The client doesn't blink. "We could make it work with VS2015 too, that way you're compatible with the next Windows 10 service pack," you continue. The client still doesn't blink. "We put our best people on it... it'll cost more but you'll get it faster." The client finally blinks. "OK, make it happen," they say.
//Solution: zproject does Visual Studio.//
Here's a way to see all the targets that zproject supports. This is a growing list:
```
gsl -target:? project.xml
```
Here is the kind of thing that zproject will report:
```
Valid targets are:
android Native shared library for Android
autotools GNU build system
cmake CMake build system
cygwin Cygwin build system
debian packaging for Debian
docker packaging for Docker
java Java JNI binding
java-msvc MSVC builds for Java JNI binding
mingw32 Mingw32 build system
nuget Packaging for NuGet
python Python binding
qml QML binding
qt Qt binding
redhat Packaging for RedHat
ruby Ruby binding
travis Travis CI scripts
vs2008 Microsoft Visual Studio 2008
vs2010 Microsoft Visual Studio 2010
vs2012 Microsoft Visual Studio 2012
vs2013 Microsoft Visual Studio 2013
vs2015 Microsoft Visual Studio 2015
```
So to support VS2010 and VS2015, we add these two targets to our {{project.xml}}:
[[code]]
<target name = "vs2010" />
<target name = "vs2015" />
[[/code]]
And then we regenerate the project packaging with {{gsl project.xml}}. Here is what zproject reports now:
```
globdom> gsl project.xml
GSL/4.1c Copyright (c) 1996-2016 iMatix Corporation
gsl/4 I: Processing project.xml...
gsl/4 M: Building GNU build system (autotools)
gsl/4 M: Building CMake build system (cmake)
gsl/4 M: Building Travis CI scripts (travis)
gsl/4 M: Building Microsoft Visual Studio 2010 (vs2010)
gsl/4 M: Building Microsoft Visual Studio 2015 (vs2015)
```
You can generate a single target using the {{-target}} command line switch. E.g.:
```
gsl -target:vs2010 project.xml
```
When you run {{git status}} you will now see a new subdirectory called {{builds}}. Add this to your repository, and commit it:
```
git add builds
git add -u
git commit -m "Problem: my client is using Windows
Solution: add Visual Studio targets"
git push origin master
```
Your repository now holds project files for two versions of Visual Studio.
Remember this lesson:
> zproject supports a lot of targets.
++ Problem: my client wants a stable release
Hold on to your horses, cowboy! One thing at a time. What the client //wants// and what the client //needs// are often two different things. Don't throw the word "stable" around lightly. It means different things to different people:
* It means stuff that is firm, robust, and won't crash.
* It means stuff that is resistant to change.
* It means stuff that isn't screamingly insane.
* It means stuff you put your horses in.
Our code is definitely one of these, and definitely not one of these. The other two, //meh//. As a compromise, let's "tag" the release. This is somewhat like shooting an orange dart, labeled "Little Buffy", into a wild buffalo. The tag is a short way of saying "that raging mountain of anger heading straight for you," or as we say in the trade, commit #bbc2c1.
//Solution: tag this version.//
Here is how we tag the current commit and send that off to GitHub:
```
git tag -m "This is version 0.2" version-0.2
git push --tags origin master
```
Here's a subtle yet important detail. We change the version number, then we do work, and then we tag it. And then we change the version number again. That means after making our "stable release", we right away update the version number, then regenerate everything, then push it to GitHub again.
In project.xml:
```
<version major = "0" minor = "3" />
```
And then:
```
gsl project.xml
git add -u
git commit -m "Problem: code version needs updating
Solution: update to version 0.3"
git push origin master
```
Remember this lesson:
> The version in git master is always the //next version you intend to release//.
++ Problem: how does my client get a tagged release?
//Solution: use GitHub's release page.//
GitHub offers one-click downloads of the tarballs and zip files for [https://github.com/scalable-c/globdom/releases/tag/version-0.2 any given release of a project]. There really isn't a better way of distributing tarballs.The ZeroMQ project uses a download server, yet that's a historical artifact, not a preference over GitHub.
If you want a specific tagged release from git, there are two ways. Either way you first clone the git repository. Then you can do either of these:
```
git checkout version-0.2
git reset --hard version-0.2
```
I tend to use the second form, as it leaves me with a clean state. It is exactly as if I rewound history. The first form leaves the repository in a "detached head" state. Unless you are a masochist, forget it. The hard reset is clean and effective. You can make commits, and they'll be in the right place. Just do {{git pull origin master}} before sending your commits back to GitHub.
Remember this lesson:
> When it comes to git, ignorance is bliss.
++ Problem: how do I build my project on Windows?
If you use Linux in your work, then Windows can be a bit of a mystery. I especially hate the Visual Studio user interface. It greeted me by asking me to register a new user account. It then tried to show me "cool" stuff about my project. The only cool stuff in a C project is the code, which seemed impossible to find. Windows 1.0 introduced flat frames without overlap, and Visual Studio has been trying to get back there ever since.
Happily you can entirely ignore Visual Studio's aspirations to be an "environment", and treat it just like any other C/C++ compiler.
//Solution: Windows has a command line.//
I'll summarize what you need to have, know, and do in order to build your project via the command line:
* You need a PC running Windows 8 or 10 with Internet connection.
* You need a Visual Studio compiler. You can use the Community Edition for free.
* Ensure the {{programfiles}} environment variable is set properly. Use the command line (or PowerShell! if you want a Kung Fury-style retro experience).
* You need to install git and/or unzip for the command line. I'd recommend to build from git.
* Using git, clone Global Domination plus all dependent projects:
```
git clone https://github.com/scalable-c/globdom
git clone https://github.com/zeromq/czmq
git clone https://github.com/zeromq/libzmq
```
* Build the projects in order:
```
cd libzmq\builds\msvc\vs2010
.\build.bat
cd ..\..\..\..
cd czmq\builds\msvc\vs2010
.\build.bat
cd ..\..\..\..
cd globdom\builds\msvc\vs2010
.\build.bat
cd ..\..\..\..
```
* Laugh with glee as you realize you just accomplished months of work in a few hours.
Remember this lesson:
> It is already amazing the horse can dance. Don't expect it to also keep a rhythm.
++ Problem: Java
//Ah, thither yon sweet Java! Thy perfumed voice lulls my dreams with paths untold! Let me count the ways I love thee...// OK, done.
The main problem with Java is that people use the language. And not just a few people either. It is the COBOL of the 21st Century, built by Big Software and sold to managers. If C++ suffers from "hey, how abstract can we make this stuff before our brains strangle themselves?" syndrome, then Java suffers from "just one more path, I promise!" pain.
Inevitably, a quiet and yet determined character will approach you on a dark street corner and offer you money. "Just give me Global Domination in Java," they'll say. "Just once," they'll insist, "How bad can it be?"
The answer is, pretty bad. Java took the traditional way of integrating native C libraries into "higher-level" languages, which is to say "pain and grief." The official name for this torture chamber is JNI, which stands for "Just Nasty, Innit?" Like many parts of the thankless job of programming, it seems OK once you're used to it.
Yet writing JNI code is a mind-numbing exercise that belies the delicacy of it. You need all the grace of an elephant hopping from rock to rock in a molten lava pool. Get it wrong and badness happens. Here's a slice of JNI:
[[code]]
JNIEXPORT jstring JNICALL
Java_org_zeromq_zyre_Zyre__1_1name (JNIEnv *env, jclass c, jlong self)
{
char *name_ = (char *) zyre_name ((zyre_t *) (intptr_t) self);
jstring return_string_ = (*env)->NewStringUTF (env, name_);
return return_string_;
}
[[/code]]
I'm not going to explain this. You can find enough JNI tutorials on-line to decipher it. What I'm going to do is explain how to generate everything you need to make it Someone Else's Problem.
//Solution: use the 'java' target.//
Add this to {{project.xml}}:
[[code]]
<target name = "java" />
[[/code]]
And then {{gsl project.xml}} as usual. You'll see this output from zproject:
```
gsl/4 M: Building Java JNI binding (java)
```
And when you type {{git status}}, you will only {{project.xml}} changed, and nothing else. There is a new directory {{bindings/jni}} and it is empty.
++ Problem: {{bindings/jni}} is empty
To generate a binding we need a little more than just the class name. We need a description of the methods in the class. We call this the "API model" and it sits in a subdirectory called {{api}} and has the file extension {{.api}}. We write this file as XML, as we do all our models.
//Solution: write an API model for gdom_client.//
Create a new file {{api/gdom_client.api}} with this content:
[[code]]
<class name = "gdom_client">
Global Domination client API
<constructor>
Create a new Global Domination client
</constructor>
<destructor>
Destroy a Global Domination client
</destructor>
</class>
[[/code]]
And then run {{gsl project.xml}} as usual. It looks just like before, yet take a look at {{bindings/jni}} now. It's full of life. Let's quickly add the newly generated files to our repository:
```
git add api bindings
git add -u
git status
```
You should see a bunch of files in {{bindings/jni}}. Let's add these to git and push our changes:
```
git add bindings/jni
git add -u
git commit -m "Problem: bindings/jni is empty
Solution: add minimal API model for gdom_client"
git push origin master
```
Remember this lesson:
> Small steps will lead you on a great journey.
++ Problem: how do I build the Java binding?
This is a part of zproject that will still change. Right now, every target in zproject has its own build process. It's easier than working entirely by hand. Yet, if you want to build several targets, it's inconsistent. If this bothers you, send a patch. For instance I've thought of writing a Bash front end script that would build any given target.
Having said that...
//Solution: use gradle.//
Install Gradle if you don't already have it:
```
sudo apt-get install gradle
```
Then, install the Java Development Kit if you don't already have it installed. Check if you already have it:
```
javac
```
And then if you need it, go install a JDK. I'm not going to explain how, because documenting Java is like blowing on molten rock to cool it down. It is useless and you easily get burnt, and one of those things other people do better.
In the {{bindings/jni}} directory, this command does the work:
```
gradle build jar
gradle test
```
This calls {{javah}} to build the JNI headers in {{src/native/include}}, and then compiles the C and Java pieces to create a jar file a sharable library (.so). It then runs the library test suite.
The result is a pretty jar file: {{build/libs/gdom-jni-0.3.0.jar}}. I've no idea what people do with jars, to be honest. I assume one installs them somewhere in the seventh circle of Hell so that one's Java "apps" (the more horrid the tech, the cuter the euphemisms) can find them. If you ever figure this out, send me a pull request.
++ Problem: the JNI layer does not build on Windows
Developing on Windows means using Microsoft Visual Studio, as we already know to our regret. To build the JNI layer on Windows, we ask for the {{java-msvc}} target. This generates all the 'solutions' (note again the cute euphemism) and project files for various versions of MSVS (aka MSVC for "Microsoft Visual C/C++").
//Solution: use the 'java-msvc' target.//
Add this to {{project.xml}}:
[[code]]
<target name = "java-msvc" />
[[/code]]
And then {{gsl project.xml}} as usual. You'll see this output from zproject:
```
gsl/4 M: Building MSVC builds for Java JNI binding (java-msvc)