34

Problem

I am developing an R package and I want to increase the version automatically each time I build it. I want that to be able to associate my results to package versions. For now I was using my own ugly function to do that.

My question is: is there a way to do it better? Or, should I avoid doing that in general?

Another option

Another option I was thinking of is to install my package (hosted in github) using ´devtools::install_github´ and then save with my results (or adding to plots) the GithubSHA1 that is saved in the installed DESCRIPTION file.

For example I can get the version and GithubSHA1 like that for the ´devtools´ package:

read.dcf(file=system.file("DESCRIPTION", package="devtools"), 
         fields=c("Version", "GithubSHA1"))
 ##      Version    GithubSHA1                                
 ## [1,] "1.5.0.99" "3ae58a2a2232240e67b898f875b8da5e57d1b3a8"

My tries so far

I wrote the following function to produce a new DESCRIPTION file, with updated version and date. (Increasing the major version is something I don't mind increasing per hand)

incVer <- function(pkg, folder=".", increase="patch"){
    ## Read DESCRIPTION from installed package ´pkg´ and make new one on the specified
    ## ´folder´. Two options for ´increase´ are "patch" and "minor"
    f <- read.dcf(file=system.file("DESCRIPTION", package=pkg),
                  fields=c("Package", "Type", "Title", "Version", "Date", 
                           "Author", "Maintainer", "Description", "License", 
                           "Depends", "Imports", "Suggests"))
    curVer <- package_version(f[4])
    if(increase == "patch") {
        curVer[[1,3]] <- ifelse(is.na(curVer$patchlevel), 1, curVer$patchlevel + 1)

    } else if (increase == "minor") {
        curVer[[1,2]] <- ifelse(is.na(curVer$minor), 1, curVer$minor + 1)
        curVer[[1,3]] <- 0
    } else {
        stop(paste("Can not identify the increase argument: " , increase))
    }

    f[4] <- toString(curVer)
    ## Update also the date
    f[5] <- format (Sys.time(), "%Y-%m-%d")
    write.dcf(f, file=paste(folder, "DESCRIPTION", sep="/"))
}
alko989
  • 7,688
  • 5
  • 39
  • 62

3 Answers3

24

If you are using git, then you can use git tags to create a version string. This is how we generate the version string of our igraph library:

git describe HEAD --tags | rev | sed 's/g-/./' | sed 's/-/+/' | rev

It gives you a format like this:

0.8.0-pre+131.ca78343

0.8.0-pre is the last tag on the current branch. (The last released version was 0.7.1, and we create a -pre tag immediately after the release tag.) 131 is the number of commits since the last tag. ca78343 is the first seven character of the hex id of the last commit.

This would be great, except that you cannot have version strings like this in R packages, R does not allow it. So for R we transform this version string using the following script: https://github.com/igraph/igraph/blob/develop/interfaces/R/tools/convertversion.sh

Essentially it creates a version number that is larger than the last released version and smaller than the next versions (the one in the -pre tag). From 0.8.0-pre+131.ca78343 it creates

0.7.999-131

where 131 is the number of commits since the last release.

I put the generation of the DESCRIPTION file in a Makefile. This replaces the date, and the version number:

VERSION=$(shell ./tools/convertversion.sh)

igraph/DESCRIPTION: src/DESCRIPTION version_number
        sed 's/^Version: .*$$/Version: '$(VERSION)'/' $<     | \
        sed 's/^Date: .*$$/Date: '`date "+%Y-%m-%d"`'/' > $@

This is quite convenient, you don't need to do anything, except for adding the release tags and the -pre tags.

Btw. this was mostly worked out by my friend and igraph co-developer, Tamás Nepusz, so the credit is his.

Gabor Csardi
  • 10,705
  • 1
  • 36
  • 53
  • 1
    yow, complicated (but looks quite useful) – Ben Bolker Jun 17 '14 at 23:16
  • 1
    Not really complicated, that is the thing. Once you have the scripts, you only need to add two git tags, one of them you would add anyway. It is really a pity, that R does not support better version strings, though. :( – Gabor Csardi Jun 18 '14 at 02:42
  • Btw. this also handles "released" versions well, so if you checkout a release, then the `git` one-liner above gives the exact versions number without the `+` and the commit id, or the `-pre`. Btw.2. in our package we also include an `igraph.version()` function that gives the correct version string, i.e. `0.8.0-pre+131.ca78343`. – Gabor Csardi Jun 18 '14 at 02:46
  • Thank you very much for your answer. I took some time to go into the details and the way you describe it is really easy to understand. I will probably use some parts of your answer (I have to learn to use `sed` some time soon). I was hoping to get an answer relying more on R and less on git tags. I was sure I was missing an argument in `devtools::build`, or something. If nothing comes up, I will be really glad to give you the bounty! – alko989 Jun 23 '14 at 12:28
  • Well, I think it is in general a good idea to tie the version numbers to the version control system. You could write your own `build_package` function based on `devtools::build`, but if you don't use the VCS revisions, then it will be impossible to go back to a given package version later, which is pretty much the main point in using version numbers. – Gabor Csardi Jun 23 '14 at 13:10
  • Btw. you don't need to use `sed`, you can write the extra script in R if you want. The reason for using `sed` and other Unix tools is that they start fast, while R has a long startup time, so I tend not to use it in simple scripts like this. – Gabor Csardi Jun 23 '14 at 13:11
  • I agree about the VCS and having a connection btw verstion numbers and VCS. I think I will do exactly what you are proposing, writing a wrapper to build_package. Maybe make it commit and tag into git. – alko989 Jun 23 '14 at 13:23
  • About the unix tools, it is something that is in my todo list in a long time. – alko989 Jun 23 '14 at 13:24
  • 1
    @alko989: I think making `build_package` commit is a bad idea. First you want to build the package, so that you can test that it works. If it works (most of the time it does not, for the first try), then you can commit. Typically you build a lot of times, before you commit. Also, with the setup detailed above, you do not need to tag each commit. You only tag "released" versions. My method ensures that all commits in between will get meaningful version numbers. – Gabor Csardi Jun 23 '14 at 13:30
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/56116/discussion-between-alko989-and-gabor-csardi). – alko989 Jun 23 '14 at 13:32
  • @Gabor Csardi, have anything changed in your versioning since you answer because file `convertversion.sh` from link cannot be found? – Taz Sep 11 '17 at 21:51
  • @Taz Yeah, I gave up on it. :) – Gabor Csardi Sep 12 '17 at 19:21
  • @Gabor Csardi, ok, but how do you handle versioning problem now? – Taz Sep 14 '17 at 13:34
  • I bump the version manually. I don't bump it for every commit. – Gabor Csardi Sep 15 '17 at 08:38
4

For a simpler approach, consider using the crant tool with the -u switch. For instance,

crant -u 3

will increment the third component of the version by one. There is also Git and SVN integration, and a bunch of other useful switches for roxygenizing, building, checking etc..

krlmlr
  • 25,056
  • 14
  • 120
  • 217
2

As auto-incrementing version numbering is not going to be built into the devtools package, I figured out a way based on Gabor's answer (the link to igraph in his answer is dead btw).

When I am about to commit to our repository, I run this bash script to set the date to today and to set the version number based on the latest tag, the .9000 suffix (as suggested here in the book R Packages by Hadley Wickham) and the number of commits within that tag:

echo "••••••••••••••••••••••••••••••••••••••••••••"
echo "• Updating package date and version number •"
echo "••••••••••••••••••••••••••••••••••••••••••••"
sed -i -- "s/^Date: .*/Date: $(date '+%Y-%m-%d')/" DESCRIPTION
# get latest tags
git pull --tags --quiet
current_tag=`git describe --tags --abbrev=0 | sed 's/v//'`
current_commit=`git describe --tags | sed 's/.*-\(.*\)-.*/\1/'`
# combine tag (e.g. 0.1.0) and commit number (like 40) increased by 9000 to indicate beta version
new_version="$current_tag.$((current_commit + 9000))" # results in 0.1.0.9040
sed -i -- "s/^Version: .*/Version: ${new_version}/" DESCRIPTION
echo "First 3 lines of DESCRIPTION:"
head -3 DESCRIPTION
echo
# ... after here more commands like devtools::document() and git commit

To be clear - this script actually makes these changes to the DESCRIPTION file.

EDIT: support for hundreds - now just increases the commit sequence number by 9000. So commit #120 in tag v0.6.1 leads to 0.6.1.9120.

MS Berends
  • 4,489
  • 1
  • 40
  • 53
  • Have you tried having this command run via git's 'post-commit' hook? – mikemtnbikes Jul 16 '21 at 19:11
  • No, but definitely interested. How would that work? How could I use that to update a file, and why prefer that over doing it locally? Just curious what the benefits are :) – MS Berends Jul 17 '21 at 20:29
  • 1
    My understanding is limited, but hooks allow code to run automatically at different points of VC updating. So you should be able to set a hook that runs your script each time you commit. Seems like you'd want this as a pre-commit hook. Hope that helps you get oriented. – mikemtnbikes Jul 18 '21 at 21:55