Delete Local References Not Starting With Match - regex

The strategy that we have for the project I'm working on is to release the project in phases, aka releases. Therefore our branching strategy is to have release1 act as the master branch for all features related to the first release, release2 for the second, and so on.
Occasionally I like to clean out the local instances of my branches. The majority of the time they've either been merged into their respective release branch or they've been abandoned. This usually involves the following sequence:
$ git branch
--- prints the list ---
$ git branch -d branch1, branch2, branch3, etc...
To try and do this in one go I tried running the following command:
$ git branch | grep -v '^release.+|QA' | xargs git branch -d
The idea is that it should:
Get every branch
Grep everything that does not start with release and is also not QA
Pass the branches to the git branch -d
But what is happening is that it is delete every branch except for the branch I currently have checked out. What am I doing wrong?

You will find that git branch output is intended to be read by humans, but has some quirks (there is a * in its output to mark the active branch, each line starts with two spaces ...) that make for a poor scripting experience.
Try using git for-each-ref instead :
# here is the mantra to get all local branch names :
git for-each-ref --format="%(refname:short)" refs/heads | grep -v ...

The reason behind it is quite simple.
git branch
QA
* master
not-release1
not-release2
not-release3
release1
release2
release3
release4
Each line begins with 2 spaces. So that's why your regex is not matching. Secondly, there is an asterisk.
The asterisk would only give a minor error like error: branch '*' not found. whereas your current checked branch can't be deleted according to git. Since nothing was matching your regex, the option -v inverted that and every branch matched, deleting everything except your current branch.
error: Cannot delete branch 'master' checked out at ...
Solution to your problem
git branch | grep -v "^ *release" | grep -v "QA" | xargs git branch -d

Related

How to use gnu_parallel to run multiple executable and/or bash scripts?

I've been recently attempting to run my scripts in parallel in a more convenient way than to open a several instances of terminal and executing in scripts separately.
I've been trying to learn how to use gnu_parallel for the past couple of days and I am still a bit clueless, and hoping if someone can provide a direct example.
Suppose I have a g++ compiled code called blah.exe and a bash script called blah.sh that will run alone perfectly fine, but I want to execute them in different directories.
I've been reading
https://www.gnu.org/software/parallel/man.html#EXAMPLE:-Working-as-xargs--n1.-Argument-appending
and
https://www.biostars.org/p/182136/
but I am not totally clear about the syntax
To run these in series, I would do:
for i in 1 2 3 4
mv ./blah.exe directory$i
cd directory$i
./blah.exe all
cd ..
end
similarly
for i in 1 2 3 4
mv ./blah.sh directory$i
cd directory$i
source ./blah.sh all
cd ..
end
I am trying to under stand how I would split this load to 4 logical-threads in one command using parallel.
Could someone provide an example for this?
Thank you for your time.
Something like:
parallel --dry-run 'cd directory{}; ../blah.exe all; source ../blah.sh all' ::: {1..4}
No need to copy/move the executable, just run the same one.
No need to cd .. afterwards, as it's a new process each time.
Note this is not multi-threading, it is multi-processing.
If you want to process discontiguous directory numbers, you can use:
parallel ... ::: {1..4} 6 7 {11..14}
If you want to process all directories, you can use:
printf "%s\0" */ | parallel -0 'cd {}; pwd'
If you want to process all directories starting with FRED, you can use:
printf "%s\0" FRED*/ | parallel -0 'cd {}; pwd'

Bash Regex matches on Ubuntu but not on macOS

I have the following bash script:
#!/bin/bash
git_status="$(git status 2>/dev/null)"
branch_pattern="^(# |)On branch ([^${IFS}]*)"
echo $git_status
echo $branch_pattern
if [[ ${git_status} =~ ${branch_pattern} ]]; then
echo 'hello'
echo $BASH_REMATCH
fi
Here is the output when I run the script on Ubuntu with bash version 4:
On branch master Initial commit Untracked files: (use "git add <file>..." to include in what will be committed) test.sh nothing added to commit but untracked files present (use "git add" to track)
^(# |)On branch ([^ ]*)
hello
On branch master
However, when I run the same script on macOS with bash version 3, the regex does not match, and nothing inside the if block is executed. The rest of the output is identical. What am I missing? Does my regex need to be formatted differently on macOS/in this version of bash? Is there a flag I am missing?
I have seen similar posts about differences in regex behavior across platforms for e.g., the find command, but I have not yet found a post relevant to my issue.
It looks to me like there's a bug in the RE engine in the version of bash that macOS comes with (it's rather old -- 3.2.57). It's something to do with the ^(# |) part -- it doesn't seem to match an empty string at the beginning of the string, as it should. But I found a workaround. Apparently the bug doesn't happen if the ^ is inside the parentheses, like this:
branch_pattern="(^# |^)On branch ([^${IFS}]*)"
BTW, you shouldn't use echo $varname to print the contents of a variable. For one thing, it'll do word splitting (converting all runs of whitespace into single spaces) and wildcard expansion on the value, which can be very confusing/misleading. Try something like printf '<<%q>>\n' "$varname" instead. Its output can be a little cryptic if the variable contains weird characters, but at least it'll make it clear that there are weird things in there.
If you are only trying to get the current git branch name, there is no need for regex. Git already has this built in.
git rev-parse --abbrev-ref HEAD 2>/dev/null
This will print out the current branch name (if any)
If you are in a git repository without any commits, it will only return "HEAD"

sclite (SCTK) `make check` faliure, C++/perl/Cygwin, Safe to use Perl4 stuff?

I am currently trying to install NIST's sclite, which is part of SCTK 2.4.0 (github or newer version). I am attempting the install on Cygwin in bash. The installation is done using make.
I have gotten past the make configure and make all parts of the installation. This didn't come without some effort (See the SO posts on the first (file not recognized) and second (template/scoping) problems). When I get to the make check part of the install, a lot of the checks/tests pass, but then I get the following error.
Testing acomp.pl
No tests defined for acomp.pl
make[2]: Leaving directory '/cygdrive/c/David/programs/sctk2.4.0/sctk/src/acomp'
(cd def_art; make check)
make[2]: Entering directory '/cygdrive/c/David/programs/sctk2.4.0/sctk/src/def_art'
Testing def_art.pl
def_art.pl passed without tests
make[2]: Leaving directory '/cygdrive/c/David/programs/sctk2.4.0/sctk/src/def_art'
(cd hubscr; make check)
make[2]: Entering directory '/cygdrive/c/David/programs/sctk2.4.0/sctk/src/hubscr'
Testing hubscr.pl
./RunTests.pl
Running test 'test1-sastt', operation 'test', options '-G -f rttm -F rttm -a', directory 'test1-sastt.test'
Executing command
Error: unable to get the version for program def_art.pl with the command 'def_art.pl' at ../hubscr.pl line 419.
Error: Execution failed at ./RunTests.pl line 30.
make[2]: *** [makefile:20: check] Error 2
make[2]: Leaving directory '/cygdrive/c/David/programs/sctk2.4.0/sctk/src/hubscr'
make[1]: *** [makefile:68: checkFast] Error 2
make[1]: Leaving directory '/cygdrive/c/David/programs/sctk2.4.0/sctk/src'
make: *** [makefile:52: check] Error 2
I've done some research (described below), and I've been able to get past this problem. However, this involved including some outdated perl modules (Perl4).
My first question was how to fix this error or how to skip that part of the test. I've been able to fix the error, and if people think that it's safe, I'll put it as an answer. Note that there is one more problem with make check after this problem is fixed, but I mention how to get past that at the end.
I'm wondering if using the old Perl (Perl4::CoreLibs) is safe and/or good programming practice. Would it be better to change the source code to use Perl5 stuff?
Is there a better way altogether?
One thing I want to be sure of is that there are no critical tests further down the make check line which might fail.
System Details
$ uname -a
CYGWIN_NT-6.1 CAP-D-ENG-INT3 2.10.0(0.325/5/3) 2018-02-02 15:16 x86_64 Cygwin
$ bash --version
GNU bash, version 4.4.12(3)-release (x86_64-unknown-cygwin) ...
$ gcc --version
gcc (GCC) 6.4.0 ...
$ g++ --version
g++ (GCC) 6.4.0 ...
$ make --version
GNU Make 4.2.1
Built for x86_64-unknown-cygwin ...
$ systeminfo | sed -n 's/^OS\ *//p'
Name: Microsoft Windows 7 Enterprise
Version: 6.1.7601 Service Pack 1 Build 7601
Manufacturer: Microsoft Corporation
Configuration: Member Workstation
Build Type: Multiprocessor Free
My Attempts/Research
From the output above, we have def_art.pl passing the check because there are no checks - "def_art.pl passed without tests". However, the next thing checked, hubscr.pl, failed. The error comes from def_art.pl.
The obvious thing to do seemed to be to run def_art.pl, which I did.
$ ./src/def_art/def_art.pl
Can't locate getopts.pl in #INC
(#INC contains: /usr/local/lib/perl5/site_perl/5.26/x86_64-cygwin-threads /usr/local/share/perl5/site_perl/5.26 /usr/lib/perl5/vendor_perl/5.26/x86_64-cygwin-threads /usr/share/perl5/vendor_perl/5.26 /usr/lib/perl5/5.26/x86_64-cygwin-threads /usr/share/perl5/5.26)
at ./src/def_art/def_art.pl line 40.
So it seems to me that this is a deprecated perl file (or module, or whatever).
I dug a little further and found this discussion on a kaldi discussion from 2014. (kaldi is a speech-recognition toolkit that uses the SCTK scoring system). There are 3 sections of the discussion that I think are especially relevant, which I will link (first, second, third). I'll insert parts here:
def_art.pl is looking for getopts.pl which I coudn't find on my machine!
... [T]hese are legacy packages that are no longer supported in recent versions
of Perl 5. I don't think we should accept a dependency on them. They have
been deprecated since the beginning of Perl 5.
Instead of 'require "getopt.pl"', we should be doing
use Getopt::Std
(note: modern perl code should not call "require" for system packages).
There is a similar issue with "flush.pl" in the Perl scripts. I don't know
what the Perl 5 package name is.
... There are several places where this occurs.
I finally found that both getopts.pl and flush.pl are available from Perl4::CoreLibs. The URL that I use for wget was referenced at this site. Apparently, in other *NIX distros, the package manager can be used, e.g.
apt-get install libperl4-corelibs-perl
or
yum install perl-Perl4-CoreLibs
but I could not find an install via apt-cyg. I was able to install them from a tarball, as described in the What I'm Doing section.
One again, I'll state my main question: Is this safe/good programming practice? Is there a better solution?
If there is a better solution (using Perl 5), it seems that this link might lead the way to it.
Some other links that are possibly related: link_{n} and link{n+1} about flush.pl, link_{n+2} & link_{n+3} about getopts.pl and Perl4::CoreLibs.
What I'm Doing
$ mkdir perl_added
$ cd perl_added
$ wget http://search.cpan.org/CPAN/authors/id/Z/ZE/ZEFRAM/Perl4-CoreLibs-0.004.tar.gz
$ tar -xzf Perl4-CoreLibs-0.004.tar.gz
$ cd Perl4-CoreLibs-0.004
Rather than adding this directory's lib subdirectory to the PERLLIB environment variable with a one-time command-line, environment-variable-addition thing, I did the following.
Make a new directory in the /usr/lib directory, move the files there
$ stat /usr/lib/libperl4-corelibs-perl
stat: cannot stat '/usr/lib/libperl4-corelibs-perl': No such file or directory
# Checked that the directory didn't already exist. It didn't exist.
$ mkdir /usr/lib/libperl4-corelibs-perl
# Make each file executable, then move it into the new directory
# I'd like to come back and explain this.
$ find ./lib -type f -name "*.pl" -print0 | xargs -I'{}' -0 \
bash -c 'new_dir=/usr/lib/libperl4-corelibs-perl/; chmod +x {}; \
mv {} ${new_dir}'
Finally, I made it so that this directory will become part of the perl search path every time I use a terminal by adding the following line to my ~/.bashrc
This command adds the path to the PERLLIB environment variable. Different flavors of Linux have different syntax for adding to environment variables, make sure to find out what yours is!
export PERLLIB="/usr/bin/libperl4-corelibs-perl:$PERLLIB"
The commands I ran for this were
$ echo -e "\n\n## Allow Perl to use the files in Perl4::CoreLibs" >> $HOME/.bashrc
$ echo -e "export PERLLIB=\"/usr/lib/libperl4_corelibs_perl:$PERLLIB\"" >> $HOME/.bashrc
$ source $HOME/.bashrc
(Thanks to #melpomene for noting that the current version is 0.004, not 0.003.)
After that, I went back to the base folder of the install and ran make clean, make config, make all, and make check.
That did get me farther in the make check but not by far.
I'm wondering if using the old Perl (Perl4::CoreLibs) is safe and/or good programming practice. Would it be better to change the source code to use Perl5 stuff?
P.S. After all this, you probably want to go back and delete the folder where you untarred everything. In my case:
rm -rf /path/to/where/I/started/perl_added
The Result/Next Steps
A bunch of tests that passed and then
(cd hubscr; make check)
make[2]: Entering directory '/cygdrive/c/David/programs/sctk2.4.0/sctk/src/hubscr'
Testing hubscr.pl
./RunTests.pl
Running test 'test1-sastt', operation 'test', options '-G -f rttm -F rttm -a', directory 'test1-sastt.test'
Executing command
Unescaped left brace in regex is illegal here in regex; marked by <-- HERE in m/{_recursive_/_recur_{ <-- HERE _sive_/_si_ve_}_}/ at ../../md-eval/md-eval.pl line 1099, <DATA> line 12.
Error: MDEVAL failed
Command: md-eval.pl -nafcs -c 0.25 -o -r sastt-case1.ref.rttm.filt -s sastt-case1.sys.rttm.filt -M sastt-case1.sys.rttm.filt.mdeval.spkrmap 1> sastt-case1.sys.rttm.filt.mdeval at ../hubscr.pl line 679.
Error: Execution failed at ./RunTests.pl line 30.
make[2]: *** [makefile:20: check] Error 255
make[2]: Leaving directory '/cygdrive/c/David/programs/sctk2.4.0/sctk/src/hubscr'
make[1]: *** [makefile:68: checkFast] Error 2
make[1]: Leaving directory '/cygdrive/c/David/programs/sctk2.4.0/sctk/src'
make: *** [makefile:52: check] Error 2
Maybe this will be helpful. I will post a separate question for this issue or, if the solution is quick, I will add the solution on this post.
A Better Way
(Actually, a couple of better ways. See my comment under the question for the kaldi solution.)
In talking with colleagues and friends, it seems that there isn't anything unsafe about the Perl4 stuff. I did find a better way to get them "installed", but I'll leave the notes in the question showing the "long way" with the tarball, PERLPATH, etc.
Check that you have CPAN
$ which cpan
If you see something starting with which: no cpan in (...), you most likely don't have it. Try installing perl. For me, on Cygwin, I used
$ apt-cyg install perl
(Install apt-cyg if necessary, cf. here for instructions.)
You probably won't have to install Perl. You will likely see something like /usr/bin/cpan as the output of which cpan. If so, you're good. Enter cpan at the command prompt.
$ cpan
If it's your first time, it will ask a bunch of questions about the configuration. I just pressed "Enter" to accept the default each time, I finally got a prompt like this:
cpan shell -- CPAN exploration and modules installation (v2.18)
Enter 'h' for help.
cpan[1]>
There, I entered
cpan[1]> install Perl4::CoreLibs
The install will proceed. When it will have finished, you will be able to type exit and press "Enter", which will take you back to the bash command prompt.
cpan[2]> exit
Lockfile removed.
$
At this point, make check will still choke, but the install will complete successfully. If you want the make check to get all the way through, go to the "Getting past make check" section below. At this point, though, you can do the last two steps in the process.
$ make install
At this point I added the install path to my PATH variable. Hopefully, I'll be able to put in a link about that process. Here is a one-time solution.
$ export PATH=/path/to/sctk/bin:$PATH
Here is a lasting solution.
Now, for the last step in the installation process:
$ make doc
After the make doc, I made sure that the man pages were available. I looked on my machine until I found the place where other man files went. (Sorry, I don't have a systematic way of doing it, I just looked in a lot of places.) For me, on Cygwin, the directory was /usr/man/man1
I went into the doc directory
cd doc
and copied all of the files into the directory I had found
cp -r ./* /usr/man/man1/
Note that there are also now html and htm files in the directory that also provide documentation.
Getting past `make check`
So, you really want to see it go through without errors. You need to change the following file: src/hubscr/RunTests.pl
Originally it has the following beginning, which I have used the head command to show.
$ head -n 15 src/hubscr/RunTests.pl
#!/usr/bin/perl -w
use strict;
my $operation = (defined($ARGV[0]) ? $ARGV[0] : "test");
sub runIt{
my ($op, $testId, $options, $glm, $hub, $lang, $ref, $systems) = #_;
my $baseDir = $testId.".base";
my $outDir = $testId.($op eq "setTests" ? ".base" : ".test");
print " Running test '$testId', operation '$op', options '$options',
directory '$outDir'\n";
system ("mkdir -p $outDir");
system ("rm -fr $outDir/test* $outDir/lvc*");
### Copy files
foreach my $file($glm, $ref, split(/\s+/,$systems)){
system("cp $file $outDir");
Change it so that, after the print command, you have new lines as follows. I again use the head command to show the beginning of the file
$ head -n 63 src/hubscr/RunTests.pl
#!/usr/bin/perl -w
use strict;
my $operation = (defined($ARGV[0]) ? $ARGV[0] : "test");
sub runIt{
my ($op, $testId, $options, $glm, $hub, $lang, $ref, $systems) = #_;
my $baseDir = $testId.".base";
my $outDir = $testId.($op eq "setTests" ? ".base" : ".test");
print " Running test '$testId', operation '$op', options '$options', directory '$outDir'\n";
####DWB, 2018-05-21 Getting `make check` to work####
if ( $testId eq "test1-sastt" &&
$operation eq "test" &&
$options eq "-G -f rttm -F rttm -a" &&
$outDir eq "test1-sastt.test" ) # <problem 1>
{
print "\n";
print "\n#### SKIPPING ####";
print "\nJust kidding. That breaks the make.";
print "\nIt said: \n\n";
print "\nUnescaped left brace in regex is illegal here in regex; marked by <-- HERE in m/{_recursive_/_recur_{ <-- HERE _sive_/_si_ve_}_}/ at ../../md-eval/md-eval.pl line 1099, <DATA> line 12.";
print "\nrror: MDEVAL failed";
print "\nCommand: md-eval.pl -nafcs -c 0.25 -o -r sastt-case1.ref.rttm.filt -s sastt-case1.sys.rttm.filt -M sastt-case1.sys.rttm.filt.mdeval.spkrmap 1> sastt-case1.sys.rttm.filt.mdeval at ../hubscr.pl line 679.";
print "\nError: Execution failed at ./RunTests.pl line 30.\n\n";
print "\n"
print "\nThat's a perl legacy problem, see:"
print "\n[https://unix.stackexchange.com/a/375505/291375][1]"
print "\nI'm outta here.";
print "\n Sincerely, bballdave025";
print "\n";
print "\n";
return;
}#endof: if (<problem 1>)
if ( $testId eq "test2-sastt" &&
$operation eq "test" &&
$options eq "-G -f rttm -F rttm -a" &&
$outDir eq "test2-sastt.test" ) # <problem 2>
{
print "\n";
print "\n#### SKIPPING ####";
print "\nJust kidding. That breaks the make.";
print "\nIt said: \n\n";
print "\nError: Test test2-sastt has failed. Diff output is :";
print "\ndiff -i -x CVS -x .DS_Store -x log -x '*lur' -I '[cC]reation[ _]date' -I md-eval -r test2-sastt.test/sastt-case2.sys.rttm.filt.alignments/segmentgroup-116.html test2-sastt.base/sastt-case2.sys.rttm.filt.alignments/segmentgroup-116.html";
print "\n 45c45";
print "\n < jg.drawStringRect(\"SUB48\",0, 47, scale*656, \"left\");";
print "\n ---";
print "\n#### and a whole bunch of other draw stuff! ####";
print "\n1 at ./RunTests.pl line 61.\n\n";
print "\n"
print "\nThat looks like Java drawing code, and I don't"
print "\neven want to mess with it!"
print "\nI'm outta here.";
print "\n Sincerely, bballdave025";
print "\n";
print "\n";
return;
}#endof: if (<problem 2>)
system ("mkdir -p $outDir")
Now you should be able to get through. Try it:
make check

Include Git commit hash and/or branch name in C/C++ source

I would like to know how you can get a Git commit hash and/or other information into the contents of a C++ variable in the compiled binary without having it be part of the source that's tracked by Git.
I need to keep track of firmware release information in a compiled executable running on an embedded processor. Means to identify the particular release of the firmware binary such as meaningful filenames, MD5 checksums or even date/time stamps are not available in this closed environment (i.e., there is no filesystem).
One approach is to have the device's console output produce identifying text, such as 'Release 1.2.3', 'commit hash 1bc123...', or similar. The firmware release information is only of interest to maintenance personnel, so a trained operator could examine the console output. To implement this it could potentially involve manual editing of a version string, which is then compiled into the code and output to the console at program startup.
This is acceptable for major releases where a signoff workflow is used to double-check that the version information is correct. However, this being a manual process it is inherently unreliable. For example, what if the developer forgets to update the version information? - There is now a disconnect between the compiled code and its reported version string.
A workflow where the code is freshly compiled and downloaded each time the user wants to test the hardware is not practical in the situation in question, ie., it is quite onerous to update the firmware.
An automatic way of identifying the version of the code is thus required. In the situation in question, Git is used, and the developers regularly commit their work to feature branches. Displaying the Git commit hash, and perhaps also whether or not there are unstaged changes, would be a way of identifying the status of the source code used to compile the firmware.
The requirement is that I would like my application to have information available to it so that it is able to display:
"Git commit:[01abcdef...etc], branch: experimentalStuffDoNotRelease"
Thus, I would like to automatically include Git information, such as commit hash and branch, in the compiled C and/or C++ code.
The development environment has developers using both Windows and Linux, and uses Eclipse CDT with a relatively unsophisticated workflow of: check out; compile; download to the hardware.
I use a makefile, like so:
GIT_HASH=`git rev-parse HEAD`
COMPILE_TIME=`date -u +'%Y-%m-%d %H:%M:%S UTC'`
GIT_BRANCH=`git branch | grep "^\*" | sed 's/^..//'`
export VERSION_FLAGS=-DGIT_HASH="\"$(GIT_HASH)\"" -DCOMPILE_TIME="\"$(COMPILE_TIME)\"" -DGIT_BRANCH="\"$(GIT_BRANCH)\""
all:
g++ main.cpp $(VERSION_FLAGS)
When the makefile is run, the git hash and the time of compilation are both loaded into macros accessible within the source, like so:
#include <iostream>
int main(){
std::cerr<<"hash="<<GIT_HASH<<", time="<<COMPILE_TIME<<", branch="<<GIT_BRANCH<<std::endl;
}
Which gives output like:
hash=35f531bf1c959626e1b95f2d3e1a7d1e4c58e5ec, time=2017-05-18 04:17:25 UTC, branch=master
In Eclipse CDT, use a pre-build step to generate an include file containing the relevant information, and a conditional inclusion to check that the file was created:
Right-click the project
Select Properties
Expand the C/C++ Build
Select Settings In the Build Steps tab
Enter the following in the Command text box:
git log --pretty=format:'#define GIT_INFO_PRESENT%n static const char* GIT_INFO = "Version Information=[%H,%d]\r\n";' -n 1 > ../src/gitcommit.h
This will, upon build, create a file gitcommit.h that will be included in the source code. To customise it, adjust the string to your needs. (See https://git-scm.com/docs/pretty-formats)
As an example, I produce a debug output at the beginning of the main routine to inform the user of the commit and branch (not strictly needed knowing the commit, but certainly helpful):
Put this in the file, probably at the top
#if __has_include("gitcommit.h")
#include "gitcommit.h"
#else
static const char* GIT_INFO = "Git version information not present.\r\n";
#endif
To display the information somewhere in your code, do similar to this:
printf(GIT_INFO);
Note that I haven't, in this case, made the pre-build step a shell script or Windows/DOS .bat file, as I work often in Linux or Windows.
Note that this isn't tested in Windows.
In both cases, 'git' must be executable from the standard command line.
There is a dependency on provision of __has_include. This was intended to provide simplicity so that a default include file need not be provided.
Note that the gitcommit.h file's path should be discoverable by the compiler.
Usually as part of your build you run some command that generates something like that.
For example, git describe gives you something that you could use:
echo // auto generated version: > version.h
git describe > echo // auto generated version: > version.h
echo -e "#define VERSION " >> version.h
git describe >> version.h
For example x264 use this simple script to generate it:
if [ -d .git ] && command -v git >/dev/null 2>&1 ; then
localver="$(($(git rev-list HEAD | wc -l)))"
if [ "$localver" -gt 1 ] ; then
ver_diff="$(($(git rev-list origin/master..HEAD | wc -l)))"
ver="$((localver-ver_diff))"
echo "#define X264_REV $ver"
echo "#define X264_REV_DIFF $ver_diff"
if [ "$ver_diff" -ne 0 ] ; then
ver="$ver+$ver_diff"
fi
if git status | grep -q "modified:" ; then
ver="${ver}M"
fi
ver="$ver $(git rev-list -n 1 HEAD | cut -c 1-7)"
version=" r$ver"
fi
fi
echo "#define X264_VERSION \"$version\""
echo "#define X264_POINTVER \"0.$ver\""
This will generate something like:
#define X264_VERSION " r2708 86b7198"
#define X264_POINTVER "0.148.2708 86b7198"
Here's simple solution if you need git hash and local modifications flag only:
$ cat update-version-info.sh
#!/bin/sh
version=$(git describe --always --dirty --tags)
echo "#define GIT_VERSION \"$version\"" > git-version.h
$ cat 1.c
#include <stdlib.h>
#include <stdio.h>
#include "git-version.h"
int main() {
#ifdef GIT_VERSION
printf("%s\n", GIT_VERSION);
#endif
return 0;
}
$ ./1
ee4f307-dirty
Based on #Richard's answer and my fixes for qmake/Qt, here is the solution I use:
With Qt 5.14 the following lines in .pro file work for me:
GIT_HASH="\\\"$$system(git -C \""$$_PRO_FILE_PWD_"\" rev-parse --short HEAD)\\\""
GIT_BRANCH="\\\"$$system(git -C \""$$_PRO_FILE_PWD_"\" rev-parse --abbrev-ref HEAD)\\\""
BUILD_TIMESTAMP="\\\"$$system(date -u +\""%Y-%m-%dT%H:%M:%SUTC\"")\\\""
DEFINES += GIT_HASH=$$GIT_HASH GIT_BRANCH=$$GIT_BRANCH BUILD_TIMESTAMP=$$BUILD_TIMESTAMP
In your code you can check the revision like this:
int main(int argc, char *argv[])
{
QStringList args;
for (int i = 0; i < argc; i++)
args << QString(argv[i]);
if (args.contains("-v") || args.contains("--version")) {
std::cout << QString("branch: %1, version: %2, built_at: %3").arg(GIT_BRANCH).arg(GIT_HASH).arg(BUILD_TIMESTAMP).toUtf8().constData() << std::endl;
return 0;
}
// ...
}
A C file or a C++ file can be generated by some program (for example, some shell script on Linux, or some GNU awk script, or your C program running system or popen(3) running some git command).
You just configure your build automation tool (e.g. your Makefile if you use GNU make, or your build.ninja file if you use ninja) appropriately.
Both Bismon and RefPerSys are doing so and keep their git id inside the obtained executable (displaying it when invoked with --version). Note: both are projects I created.
PS a different question is how to configure your IDE to run a particular build automation tool. This is specific to your IDE. But Eclipse CDT FAQ offers an insight.

GNU Make - Set MAKEFILE variable from shell command output within a rule/target

I'm trying to put together some complicated makefile rules to automate building a project against multiple compilers. I have one rule that creates some dynamically generated variables and assigns variables to them, then passes those variables along to a call to build a separate rule.
.PHONY: all
all:
#echo "Detected CPULIST:${CPULIST_DETECTED}"
#echo "CPULIST:${CPULIST}"
#for cpu in $(CPULIST_DETECTED); do \
echo "CPU:$${cpu}:"; \
eval VARIANTLIST_DETECTED=$(shell 2>&1 find ./.build/linux/$$cpu -mindepth 1 -maxdepth 1 | grep -v '\.svn' | grep -v warning ); \
eval echo "Detected Variant List:$${VARIANTLIST_DETECTED}"; \
eval variant_$${cpu}=$${cpu}; \
eval echo "variant_\$${cpu}:\$${variant_$${cpu}}"; \
$(MAKE) build CPULIST=$${cpu}; \
done
.PHONY: build
build: sanity_check $(TARGET)
#true
I'm having two issues. The first is that, despite double-escaping cpu via $$cpu, it translates to null in the line:
eval VARIANTLIST_DETECTED=$(shell 2>&1 find ./.build/linux/$$cpu -mindepth 1 -maxdepth 1 | grep -v '\.svn' | grep -v warning ); \
So, the find command searches against ./.build/linux/ for each iteration of the loop rather than looping through ./.build/linux/arm and ./.build/linux/x86 like I would expect. I've included a post where this is described in the references below. I suspect this might be causing a problem because I'm attempting this within a rule itself rather than in the global assignment portion of the makefile (before the rules).
The other problem is occurring at the exact same line. It seems that the shell command is evaluated, assigned to VARIANTLIST_DETECTED, but then VARIANTLIST_DETECTED is executed as if it were a command, since I get the following error during build:
Detected CPULIST:arm x86
CPULIST:
CPU:arm:
/bin/sh: 3: ./.build/linux/x86: Permission denied
Detected Variant List:
variant_arm:arm
It's attempting to run the first result in my query as if it were a command. That line should also be something like ./.build/linux/x86/so.
How do I go about resolving these two issues? They are the last two thorns impeding completion of my makefiles.
Thanks!
References
Assign a makefile variable value to a bash command result?, Accessed 2014-06-19, <https://stackoverflow.com/questions/2373081/assign-a-makefile-variable-value-to-a-bash-command-result>
You need to step back a little bit and think about what's going on here: you seem to just be throwing things into your makefile and hoping the result is what you want, rather than really understanding what's happening and proceeding with a purpose to solve your problem.
The first thing to understand is how make invokes a recipe: first, make expands the recipe. Expanding means make looks through the text of the recipe and finds anything that begins with a $ and treats it like a make function or variable, and evaluates it. So, $$ is converted into a single $, $(CPU_LIST_DETECTED) is expanded to the value of that variable, and $(shell ....) is a make function which is run and the results are included in the recipe text. Maybe you can already see where your recipe is going wrong...
Then second, AFTER all of the recipe has been expanded, make takes the expanded text of the recipe and passes it to the shell. Make then waits for the shell to complete, and make looks at the exit code of the shell. If it's 0, then the recipe succeeds and make continues. If it's not 0, then the recipe fails and make exits with an error. There is NO INTERACTION between make and the shell except make starts it with a script to run, and gets back the exit code. If you couldn't before, maybe you can now see where your recipe is going wrong.
It's virtually always an error (or at least unnecessary) to use a $(shell ...) function inside a recipe... the recipe is already running in the shell! You don't need make to run another shell first, then give those results to a new shell.
Your shell invocation is trying to use a shell variable, $cpu. But that variable has no value here, when make runs the $(shell ...) function, because make runs that function before it starts your shell script. You can't have things in your make functions that refer to things that won't be defined until the recipe is running. This will run the shell script 2>&1 find ./.build/linux/$cpu -mindepth 1 -maxdepth 1 | grep -v '\.svn' | grep -v warning, but the shell variable cpu is not set so the find will recurse through everything under ./.build/linux/.
Second, you are misusing eval here. The shell function expands to a list of words, say foo bar baz. Then the eval will look like this:
eval VARIANTLIST_DETECTED=foo bar baz
which is the same as writing just this:
VARIANTLIST_DETECTED=foo bar baz
which is the same as running the command bar with an argument baz, after setting the variable VARIANTLIST_DETECTED to the value foo. Which is why it seems to be "running your directory".
You probably wanted this:
VARIANTLIST_DETECTED="foo bar baz"
(note quotes). However, getting quotes through an eval is tricky" you have to escape them. Luckily you don't need eval at all here anyway, because you're not trying to assign a dynamically named variable. You can just write the assignment directly.
So, losing the shell function and the eval, that line should be written:
VARIANTLIST_DETECTED=`2>&1 find ./.build/linux/$$cpu -mindepth 1 -maxdepth 1 | grep -v '\.svn' | grep -v warning`; \
Ditto for the echo: you don't need the eval there.
However, I really don't see the point of setting this variable or the variant_$cpu variable either. You never do anything with them.
In general, try to avoid putting Make syntax inside a command. Try this:
VARIANTLIST_DETECTED=$$(find ./.build/linux/$${cpu} -mindepth 1 -maxdepth 1 | grep -v '.svn' | grep -v warning );