diff --git a/doc/src/vcs.rst b/doc/src/vcs.rst new file mode 100644 index 0000000000..38e44ab542 --- /dev/null +++ b/doc/src/vcs.rst @@ -0,0 +1,182 @@ +Boost.Build ``vcs`` Module +========================== + +.. contents:: + +Overview +-------- + +The Boost.Build ``vcs`` module exposes a limited subset of version +control system functionality to Boost.Build projects for a set of +supported version control system back ends. Currently, Boost.Build +``vcs`` supports Subversion and Git. Other systems should be +straightforward to implement. + +Usage +----- + +The following example illustrates the use of the ``vcs`` module. + +.. code:: + + import vcs ; + + import assert ; + + # print the type of version control system and the generated + # version string for this project + echo [ vcs.type . ] + echo [ vcs.generate-version-string . ] ; + + # fetch and checkout the 1.0 reference of a project kept in the Git + # version control system + vcs.get git : https://example.com/git/path/to/project/root : /path/to/desired/root ; + vcs.checkout : /path/to/desired/root : 1.0 ; + + # verify that the URL and reference matches the desired + assert.equal [ vcs.root-url /path/to/desired/root ] : https://example.com/git/path/to/desired/root ; + assert.equal [ vcs.ref /path/to/desired/root ] : [ vcs.ref /path/to/desired/root : 1.0 ] ; + +The `example/vcs <../../example/vcs>`_ directory in the source +repository contains a working example of the ``vcs`` module. + +The `example/vcs-generate-version-string +<../../example/vcs-generate-version-string>`_ directory in the source +repository contains the complete source code to generate a version +string using the ``vcs`` module. The listings below illustrate the +use of ``vcs.generate-version-string`` to create a +``version_string.cpp`` file containing the version string. Note that +the ``print`` module provides a mechanism to ensure that the generated +file is only modified when the version string actually changes. + +.. include:: ../../example/vcs-generate-version-string/jamroot.jam + :code: + +.. include:: ../../example/vcs-generate-version-string/main.cpp + :code: + +Reference +--------- + +``type ( directory )`` + + Returns the type of version control system for the indicated + directory, or the empty string if none was detected. + +``generate-version-string ( directory )`` + + Returns a string uniquely describing the state of the repository at + the given directory. + + - When on a tag, all version control systems will return the tag + name + + - Otherwise + + - Git: ``---g`` + + - Subversion: ``---s`` + + The ``generate-version-string`` rule can be used to generate a version + string for a program dynamically. + +``fetch ( vcs : root-url : directory )`` + + Fetches from the URL to the root of the vcs project to the + indicated directory using vcs. + +``checkout ( directory : symbolic-ref )`` + + Checks out the indicated symbolic reference from the repository + located at the indicated directory. + +``root-url ( directory )`` + + Returns the URL to the root of the vcs project located at the + indicated directory. + +``ref ( directory : symbolic-ref ? )`` + + Returns a unique identifier representing the current state of the + vcs project located at directory. If the symbolic reference is + given, the rule returns the reference of that symbolic reference, + not the current state of the project. + +Backends Reference +------------------ + +``generate-version-string ( directory )`` + + Returns the version string as defined for the backend. Note that + each backend is required to return the exact tag name if the + directory is on a tag. Otherwise, the format is free-form, but it + is recommended that it be as close to the Git format for ``git + describe`` as possible for maximum information. + +``fetch ( root-url : directory )`` + + Fetches the from the URL to the root of the vcs project to the + indicated directory using the backend. + +``checkout ( directory : symbolic-ref )`` + + Checks out the indicated symbolic reference from the repository + located at the indicated directory. + +``root-url ( directory )`` + + Returns the URL to the root of the vcs project located at the + indicated directory. + +``ref ( directory : symbolic-ref ? )`` + + Returns a unique identifier representing the current state of the + vcs project located at directory. If the symbolic reference is + given, the rule returns the reference of that symbolic reference, + not the current state of the project. + +``is-repository ( directory )`` + + Returns true if the directory is controlled by the backend version + control system. This can be as complex or as simple as required. + +``executable-exists ( )`` + + Returns true if the executable required to support the backend + exists on the system. + +Design +------ + +The Boost.Build ``vcs`` module depends on separate backends to +implement the interface. The backend file should be named +``vcs-BACKEND.jam`` where ``BACKEND`` is the name of the backend and +should contain implementations for each of the functions defined +below. + +Currently, there are two supported backends: + +- Git +- Subversion + +Note that the only rule that requires the type of version control +system to be specified is the ``fetch`` rule. The rest of the rules +detect the version control system from querying the given directory. + +Implementation +-------------- + +Hopefully, knowing the implementation will not be required to use this +module, but a link to the implementation and links to the backends are +included here for reference. + +``vcs`` Interface +~~~~~~~~~~~~~~~~~ + +- `vcs <../../src/tools/vcs.jam>`_ + +Backends +~~~~~~~~ + +- `vcs-git <../../src/tools/vcs-git.jam>`_ +- `vcs-svn <../../src/tools/vcs-svn.jam>`_ diff --git a/doc/src/vcs.xml b/doc/src/vcs.xml new file mode 100644 index 0000000000..d7a0943a30 --- /dev/null +++ b/doc/src/vcs.xml @@ -0,0 +1,366 @@ + + + +
+ + vcs + + vcs + module + + +
+ Overview + + The Boost.Build vcs module exposes a limited subset + of version control system functionality to Boost.Build projects + for a set of supported version control system back ends. + Currently, Boost.Build vcs supports Subversion and + Git. Other systems should be straightforward to implement. + +
+ +
+ Usage + + The following example illustrates the use of the vcs module. + + + + + jamroot.jam + +import vcs ; + +import assert ; + +# print the type of version control system and the generated +# version string for this project +echo [ vcs.type . ] +echo [ vcs.generate-version-string . ] ; + +# fetch and checkout the 1.0 reference of a project kept in the Git +# version control system +vcs.get git : https://example.com/git/path/to/project/root : /path/to/desired/root ; +vcs.checkout : /path/to/desired/root : 1.0 ; + +# verify that the URL and reference matches the desired +assert.equal [ vcs.root-url /path/to/desired/root ] : https://example.com/git/path/to/desired/root ; +assert.equal [ vcs.ref /path/to/desired/root ] : [ vcs.ref /path/to/desired/root : 1.0 ] ; + + + + + + The example/vcs directory in the source + repository contains a working example of the vcs module. + + + + The + example/vcs-generate-version-string directory in the source + repository contains the complete source code to generate a version + string using the vcs module. The listings below illustrate the + use of vcs.generate-version-string to create a + version_string.cpp file containing the version string. Note that + the print module provides a mechanism to ensure that the generated + file is only modified when the version string actually changes. + + + + + jamroot.jam + + + + + + + + + + main.cpp + + + + + + +
+ +
+ Reference + + + + + + type + vcs + + rule type ( directory ) + + Returns the type of version control system for the indicated + directory, or the empty string if none was detected. + + + + + + generate-version-string + vcs + + rule generate-version-string ( directory ) + + Returns a string uniquely describing the state of the repository at + the given directory. + + + + + When on a tag, all version control systems will return the tag + name. + + + + + + Otherwise: + + + + Git: <nearest-tag-name>-<branch-name>-<commits-since-nearest-tag>-g<commit-id> + + + + + Subversion: -<URL>--s<REV> + + + + + + + + + + The generate-version-string rule can be used to generate a version + string for a program dynamically. + + + + + + fetch + vcs + + rule fetch ( vcs : root-url : directory ) + + Fetches the from the URL to the root of the vcs project to the + indicated directory using vcs. + + + + + + checkout + vcs + + rule checkout ( directory : symbolic-ref ) + + Checks out the indicated symbolic reference from the repository + located at the indicated directory. + + + + + + root-url + vcs + + rule root-url ( directory ) + + Returns the URL to the root of the vcs project located at the + indicated directory. + + + + + + ref + vcs + + rule ref ( directory : symbolic-ref ? ) + + Returns a unique identifier representing the current state of the + vcs project located at directory. If the symbolic reference is + given, the rule returns the reference of that symbolic reference, + not the current state of the project. + + + + + +
+ +
+ Backends Reference + + + + + + generate-version-string + vcs + + rule generate-version-string ( directory ) + + Returns the version string as defined for the backend. Note that + each backend is required to return the exact tag name if the + directory is on a tag. Otherwise, the format is free-form, but it + is recommended that it be as close to the Git format for git + describe as possible for maximum information. + + + + + + fetch + vcs + + rule fetch ( root-url : directory ) + + Fetches the from the URL to the root of the vcs project to the + indicated directory using the backend. + + + + + + checkout + vcs + + rule checkout ( directory : symbolic-ref ) + + Checks out the indicated symbolic reference from the repository + located at the indicated directory. + + + + + + root-url + vcs + + rule root-url ( directory ) + + Returns the URL to the root of the vcs project located at the + indicated directory. + + + + + + ref + vcs + + rule ref ( directory : symbolic-ref ? ) + + Returns a unique identifier representing the current state of the + vcs project located at directory. If the symbolic reference is + given, the rule returns the reference of that symbolic reference, + not the current state of the project. + + + + + + is-repository + vcs + + rule is-repository ( directory ) + + Returns true if the directory is controlled by the backend version + control system. This can be as complex or as simple as required. + + + + + + executable-exists + vcs + + rule executable-exists ( ) + + Returns true if the executable required to support the backend + exists on the system. + + + + +
+ +
+ Design + + The Boost.Build vcs module depends on separate backends to + implement the interface. The backend file should be named + vcs-BACKEND.jam where BACKEND is the name of the backend and + should contain implementations for each of the functions defined + below. + + + + Currently, there are two supported backends: + + + Git + Subversion + + + + + Note that the only rule that requires that that the type of version + control system is specified is the fetch rule. The rest detect + the version control system from querying the given directory. + +
+ +
+ Implementation + + Hopefully, knowing the implementation will not be required to use this + module, but a link to the implementation and links to the backends are + included here for reference. + + +
+ <code>vcs</code> Interface + + + src/tools/vcs.jam + + +
+ +
+ Backends + + + + src/tools/vcs-git.jam + + + src/tools/vcs-svn.jam + + +
+
+ +
diff --git a/example/vcs-generate-version-string/.gitignore b/example/vcs-generate-version-string/.gitignore new file mode 100644 index 0000000000..ae3c172604 --- /dev/null +++ b/example/vcs-generate-version-string/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/example/vcs-generate-version-string/README.rst b/example/vcs-generate-version-string/README.rst new file mode 100644 index 0000000000..fad935a1c7 --- /dev/null +++ b/example/vcs-generate-version-string/README.rst @@ -0,0 +1,16 @@ +Generate a Version String from VCS +================================== + +This example shows how to generate a version string directly from the +VCS system used to check out Boost.Build using the ``vcs`` module. + +This Boost.Build project generates a program to print out the version +string of the current working directory. It should be run with +``--verbose-test`` so the result is easy to see. + +.. code:: + + b2 --verbose-test + +Note that the ``version_string.cpp`` file can be found in the ``bin`` +directory tree. diff --git a/example/vcs-generate-version-string/jamroot.jam b/example/vcs-generate-version-string/jamroot.jam new file mode 100644 index 0000000000..5079419fc3 --- /dev/null +++ b/example/vcs-generate-version-string/jamroot.jam @@ -0,0 +1,30 @@ +# A Jamroot to run a program that prints a generated version string. + +import testing ; + +import vcs ; +import path ; +import print ; + +if ! [ path.exists wd-git ] +{ + vcs.fetch git : https://github.com/boostorg/build.git : wd-git ; +} +vcs.checkout wd-git : 2016.03 ; + +# run it to see the output +run versioned : : : : versioned-run ; + +# note that version_string.cpp is generated below +exe versioned : main.cpp version_string.cpp ; + +# generate the version_string.cpp file +make version_string.cpp : : @generate-file ; +rule generate-file ( target : sources * : properties * ) +{ + local v = [ vcs.generate-version-string wd-git ] ; + + print.output $(target) ; + print.text "const char * version_string = \"$(v)\";" : true ; + print.text "" ; +} diff --git a/example/vcs-generate-version-string/main.cpp b/example/vcs-generate-version-string/main.cpp new file mode 100644 index 0000000000..bad43498c5 --- /dev/null +++ b/example/vcs-generate-version-string/main.cpp @@ -0,0 +1,12 @@ +// A program to print the version string. +#include + +extern const char * version_string; + +int +main () +{ + std::cout << version_string << "\n"; + + return 0; +} diff --git a/example/vcs/.gitignore b/example/vcs/.gitignore new file mode 100644 index 0000000000..e14fcd1b13 --- /dev/null +++ b/example/vcs/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/repositories/ diff --git a/example/vcs/README.rst b/example/vcs/README.rst new file mode 100644 index 0000000000..84f5da733c --- /dev/null +++ b/example/vcs/README.rst @@ -0,0 +1,27 @@ +Boost.Build vcs Module Demonstration +==================================== + +This Boost.Build project provides a demonstration of the functionality +available from the ``vcs`` module. + +It first prints the properties of the VCS system used to maintain this +repository, then fetches and checks out the Boost.Build source code +form GitHub using both Git and Subversion and prints the properties of +each of those repositories. + +This will create the following directory tree by checking out the +Boost.Build source code at the indicated versions. + +:: + + repositories + repositories/git + repositories/gitboost-build-boost-1.55.0 + repositories/gitboost-build-master + repositories/gitboost-build-origin-master + repositories/gitboost-build-refs-heads-master + repositories/gitboost-build-refs-tags-boost-1.55.0 + repositories/gitboost-build-tags-boost-1.55.0 + repositories/svn + repositories/svn/boost-build-boost-tags-1.55.0 + repositories/svn/boost-build-trunk diff --git a/example/vcs/jamroot.jam b/example/vcs/jamroot.jam new file mode 100644 index 0000000000..d46a30e119 --- /dev/null +++ b/example/vcs/jamroot.jam @@ -0,0 +1,13 @@ +import vcs ; + +import vcs-helper ; + +# fetch and checkout the Boost.Build source directory +vcs-helper.execute git : https://github.com/boostorg/build.git : master : boost-build-master ; +vcs-helper.execute git : https://github.com/boostorg/build.git : refs/heads/master : boost-build-refs-heads-master ; +vcs-helper.execute git : https://github.com/boostorg/build.git : origin/master : boost-build-origin-master ; +vcs-helper.execute git : https://github.com/boostorg/build.git : boost-1.55.0 : boost-build-boost-1.55.0 ; +vcs-helper.execute git : https://github.com/boostorg/build.git : tags/boost-1.55.0 : boost-build-tags-boost-1.55.0 ; +vcs-helper.execute git : https://github.com/boostorg/build.git : refs/tags/boost-1.55.0 : boost-build-refs-tags-boost-1.55.0 ; +vcs-helper.execute svn : https://github.com/boostorg/build.git : trunk : boost-build-trunk ; +vcs-helper.execute svn : https://github.com/boostorg/build.git : tags/boost-1.55.0 : boost-build-boost-tags-1.55.0 ; diff --git a/example/vcs/vcs-helper.jam b/example/vcs/vcs-helper.jam new file mode 100644 index 0000000000..ab0dbd59bc --- /dev/null +++ b/example/vcs/vcs-helper.jam @@ -0,0 +1,35 @@ +import path ; + +import vcs ; + +# fetch, checkout, and analyze a vcs repository +rule execute ( vcs : root-url : symbolic-ref : path ) +{ + echo "------------------------------------------------------------------------------" ; + + # a place to put the test repositories + local tmp = repositories/$(vcs) ; + + local desired-url = $(root-url) ; + local desired-symbolic-ref = $(symbolic-ref) ; + local desired-dir = $(tmp)/$(path) ; + + if ! [ path.exists $(desired-dir) ] + { + vcs.fetch $(vcs) : $(desired-url) : $(desired-dir) ; + } + vcs.checkout $(desired-dir) : $(desired-symbolic-ref) ; + + local actual-vcs = [ vcs.type $(desired-dir) ] ; + local actual-root-url = [ vcs.root-url $(desired-dir) ] ; + local actual-head-ref = [ vcs.ref $(desired-dir) ] ; + local actual-symbolic-ref = [ vcs.ref $(desired-dir) : $(desired-symbolic-ref) ] ; + local actual-version-string = [ vcs.generate-version-string $(desired-dir) ] ; + + echo "info: analyzing $(desired-dir):" ; + echo " type: $(actual-vcs)" ; + echo " root URL: $(actual-root-url)" ; + echo " head reference: $(actual-head-ref)" ; + echo " symbolic reference: $(actual-symbolic-ref)" ; + echo " generated version string: $(actual-version-string)" ; +} diff --git a/src/tools/vcs-git.jam b/src/tools/vcs-git.jam new file mode 100644 index 0000000000..5812510c12 --- /dev/null +++ b/src/tools/vcs-git.jam @@ -0,0 +1,231 @@ +# Copyright 2016 +# +# Distributed under the Boost Software License, Version 1.0. (See +# accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +# Version Control System - Git +# +# @todo detect if git is available +# @todo detect if the current repository is a git repository + +import os ; +import path ; +import errors ; +import assert ; + +# @todo SHELL commands below need to be abstracted to support all +# systems. +if [ os.name ] = VMS +{ + errors.error "VMS is not supported at this time." ; +} + +# Returns a version string representing the version of the Git +# repository. +# +# If on a tag: +# +# +# +# If not on a tag: +# +# ---g +# +# If the repository is dirty, "-dirty" will be appended. +# +rule generate-version-string +( + directory # A directory resulting from a fetch or checkout. +) +{ + local v = "" ; + + if ! [ executable-exists ] + { + errors.user-error "vcs-git: Git executable is not installed" ; + } + + if ! [ is-repository $(directory) ] + { + errors.user-error "vcs-git: $(directory) is not a Git repository." ; + } + + if [ os.name ] = NT + { + v = [ SHELL "(cd $(directory) && git describe --tags --exact-match --dirty) 2> NUL" ] ; + } + else + { + v = [ SHELL "(cd $(directory) && git describe --tags --exact-match --dirty) 2> /dev/null" ] ; + } + v = [ SPLIT_BY_CHARACTERS $(v) : "\n" ] ; + + if $(v) != "" + { + local m = [ MATCH "^(.+)$" : $(v) ] ; + if $(m) + { + v = $(m[1]) ; + } + } + else + { + local v0 = [ SHELL "(cd $(directory) && git describe --tags --long --dirty)" ] ; + v0 = [ SPLIT_BY_CHARACTERS $(v0) : "\n" ] ; + local m0 = [ MATCH "^(.+)-([0-9]+)-g([0-9a-fA-F]+)(-dirty)?$" : $(v0) ] ; + if ! $(m0) + { + m0 = "" "" ; + } + + local v1 = [ SHELL "(cd $(directory) && git describe --all --long --dirty)" ] ; + v1 = [ SPLIT_BY_CHARACTERS $(v1) : "\n" ] ; + local m1 = [ MATCH "^(heads|remotes/.+)/(.+)-([0-9]+)-g([0-9a-fA-F]+)(-dirty)?$" : $(v1) ] ; + assert.variable-not-empty m1 ; + + v = "$(m0[1])-$(m0[2])-$(m1[2])-g$(m1[4])" ; + if $(m1[5]) + { + v = "$(v)-dirty" ; + } + } + + return $(v) ; +} + +# Fetches from the given url to the given directory. +# +# git clone --recurse-submodules $(root-url) $(directory) +# +rule fetch +( + root-url : # The root URL of the repository from which to fetch. + directory # A directory resulting from a fetch or checkout. +) +{ + # @todo check results + local r = [ SHELL "git clone --quiet $(root-url) $(directory)" ] ; + + # @todo should work, but doesn't + # assert.true path.exists $(directory) ; +} + +# Checks out the indicated symbolic reference for the Git +# repository at directory. +# +# A symbolic reference for Git is a URI based off the 'refs/' base or +# anything else that looks like a reference or a commit. +# +# refs/heads/master +# refs/tags/1.1.1 +# refs/heads/devel-fixes +# +rule checkout +( + directory : # A directory resulting from a Git fetch or checkout. + symbolic-ref ? # An optional Git-specific symbolic reference. +) +{ + symbolic-ref = [ normalize-symbolic-ref $(symbolic-ref) ] ; + + # check errors, etc. + local r = [ SHELL "(cd $(directory) && git checkout --quiet $(symbolic-ref))" ] ; + local s = [ SHELL "(cd $(directory) && git submodule --quiet update --init --recursive)" ] ; +} + +# Returns the root URL of the Git repository at the given directory. +# +rule root-url +( + directory # A directory resulting from a fetch or checkout. +) +{ + local u = ; + + local output = [ SHELL "(cd $(directory) && git remote -v)" ] ; + local lines = [ SPLIT_BY_CHARACTERS $(output) : "\n" ] ; + for local line in $(lines) + { + local m = [ MATCH "^origin[ ]+(.+) .fetch." : $(line) ] ; + if $(m) + { + assert.equal $(u) : ; + + u = $(m[0]) ; + } + } + + assert.variable-not-empty u ; + + return $(u) ; +} + +# Returns the reference for the symbolic reference requested or HEAD +# if it is empty for the git repository at directory. +# +# For a Git repository, a reference is the SHA-1. +# +# deed12131a3334df4322 +# +rule ref +( + directory : # A directory resulting from a fetch or checkout. + symbolic-ref ? # An optional Git-specific symbolic reference. +) +{ + if ! $(symbolic-ref) + { + symbolic-ref = HEAD ; + } + symbolic-ref = [ normalize-symbolic-ref $(symbolic-ref) ] ; + + return [ SHELL "(cd $(directory) && git log -n 1 --pretty=format:\"%H\" $(symbolic-ref))" ] ; +} + +# Returns true if the given directory is a Git repository. +# +rule is-repository +( + directory # A directory resulting from a fetch or checkout. +) +{ + # @todo is there a better way? + return [ path.exists "$(directory)/.git" ] ; +} + +# Return true if the Git executable exists. +# +rule executable-exists ( ) +{ + # @todo always say true for now + return 1 == 1 ; +} + +# Returns a normalized symbolic reference. +# +# refs/heads/* -> origin/* +# +# refs/tags/* -> * +# +local rule normalize-symbolic-ref +( + symbolic-ref # A Git-specific symbolic reference. +) +{ + local m0 = [ MATCH "^(refs/)?tags/(.+)$" : $(symbolic-ref) ] ; + if $(m0) + { + symbolic-ref = $(m0[2]) ; + } + else + { + local m1 = [ MATCH "^(refs/)?heads/(.+)$" : $(symbolic-ref) ] ; + if $(m1) + { + symbolic-ref = origin/$(m1[2]) ; + } + } + + return $(symbolic-ref) ; +} diff --git a/src/tools/vcs-svn.jam b/src/tools/vcs-svn.jam new file mode 100644 index 0000000000..d94a53947a --- /dev/null +++ b/src/tools/vcs-svn.jam @@ -0,0 +1,270 @@ +# Copyright 2016 +# +# Distributed under the Boost Software License, Version 1.0. (See +# accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +# Version Control System - Subversion +# +# @todo detect if svn is available +# @todo detect if the current repository is a Subversion repository + +import os ; +import path ; +import errors ; +import assert ; + +# @todo SHELL commands below need to be abstracted to support all +# systems. +if [ os.name ] = VMS +{ + errors.error "VMS is not supported at this time." ; +} + +# Generates a version string from the Subversion repository assuming a +# standard repository layout. +# +# If on a tag: +# +# +# +# If not on a tag: +# +# @todo work to match the Git format, missing nearest tag and commits +# since nearest tag +# +# ---s +# +# If the repository is dirty, "-dirty" will be appended. +# +rule generate-version-string +( + directory # A directory resulting from a fetch or checkout. +) +{ + local v = "" ; + + if ! [ executable-exists ] + { + errors.user-error "vcs-svn: Subversion executable is not installed" ; + } + + if ! [ is-repository $(directory) ] + { + errors.user-error "vcs-svn: $(directory) is not a Subversion repository." ; + } + + local tag = "" ; + local branch = "" ; + local commits = "0" ; + local revision = "" ; + + local url = "" ; + local exact_match = "" ; + + local lines = [ SHELL "svn info $(directory)" ] ; + + lines = [ SPLIT_BY_CHARACTERS $(lines) : "\n" ] ; + + for local line in $(lines) + { + local urlm = [ MATCH "^URL: (.+)\n$" : $(line) ] ; + if $(urlm) + { + url = $(urlm[1]) ; + } + local revisionm = [ MATCH "^Revision: ([0-9]+)\n$" : $(line) ] ; + if $(revisionm) + { + revision = $(revisionm[1]) ; + } + } + + if $(url) != "" + { + tagm = [ MATCH "^.*/tags/(.+)$" : $(url) ] ; + if $(tagm) + { + exact_match = "exact_match" ; + + tag = $(tagm[1]) ; + } + + branchm = [ MATCH "^.*/branches/(.+)$" : $(url) ] ; + if $(branchm) + { + branch = $(branchm[1]) ; + } + else + { + trunkm = [ MATCH "^.*/(trunk)$" : $(url) ] ; + if $(trunkm) + { + branch = $(trunkm[1]) ; + } + } + } + + # create the version string + if $(exact_match) = "exact_match" + { + v = $(tag) ; + } + else + { + v = $(tag)-$(branch)-$(commits)-s$(revision) ; + } + + # check if the working copy is dirty + if [ SHELL "cd $(directory) && svn diff" ] != "" + { + v = "$(v)-dirty" ; + } + + return $(v) ; +} + +# Fetches from the given url to the given directory. +# +# svn checkout $(root-url)/trunk $(directory) +# +rule fetch +( + root-url : # The root URL of the repository from which to fetch. + directory # A directory resulting from a fetch or checkout. +) +{ + # @todo check results + + # @todo there may not be a trunk, what to do? + local r = [ SHELL "svn checkout $(root-url)/trunk $(directory)" ] ; + + # @todo should work, but doesn't + # assert.true path.exists $(directory) ; +} + +# Checks out the indicated symbolic reference for the Subversion +# repository at directory. +# +# A symbolic reference for Subversion is a URI based off the root URL. +# +# trunk +# tags/1.1.1 +# branches/devel-fixes +# +rule checkout +( + directory : # A directory resulting from a Subversion fetch or checkout. + symbolic-ref ? # An optional Subversion-specific symbolic reference. +) +{ + # check errors, etc. + local ru = [ root-url $(directory) ] ; + + local r = [ SHELL "( cd $(directory) && svn switch $(ru)/$(symbolic-ref) )" ] ; +} + +# Returns the root URL of the Subversion repository at the given +# directory. +# +rule root-url +( + directory # A directory resulting from a fetch or checkout. +) +{ + local u = ; + + local output = [ SHELL "( cd $(directory) && svn info )" ] ; + local lines = [ SPLIT_BY_CHARACTERS $(output) : "\n" ] ; + for local line in $(lines) + { + m = [ MATCH "^Repository Root: (.+)" : $(line) ] ; + if $(m) + { + assert.equal $(u) : ; + + u = $(m[0]) ; + } + } + + assert.variable-not-empty u ; + + return $(u) ; +} + +# Returns the reference for the symbol reference if it is not empty or +# HEAD for the Subversion repository at directory. +# +# For a Subversion repository, a reference is the URL fragment beyond +# the root and the last changed revision. +# +# tags/1.1.1@12345 +# +rule ref +( + directory : # A directory resulting from a fetch or checkout. + symbolic-ref ? # An optional Subversion-specific symbolic reference. +) +{ + local ru = [ root-url $(directory) ] ; + + local lines = ; + if ! $(symbolic-ref) + { + lines = [ SHELL "( cd $(directory) && svn info )" ] ; + } + else + { + lines = [ SHELL "svn info $(ru)/$(symbolic-ref)" ] ; + } + + assert.variable-not-empty lines ; + + local r = ; + local sr = ; + + for local line in $(lines) + { + line = [ SPLIT_BY_CHARACTERS $(line) : "\n" ] ; + + m0 = [ MATCH "^Last Changed Rev: (.+)" : $(line) ] ; + if $(m0) + { + assert.equal $(r) : ; + + r = $(m0[0]) ; + } + + m1 = [ MATCH "^URL: $(ru)/(.+)" : $(line) ] ; + if $(m1) + { + assert.equal $(sr) : ; + + sr = $(m1[0]) ; + } + } + + assert.variable-not-empty r ; + assert.variable-not-empty sr ; + + return $(sr)@$(r) ; +} + +# Returns true if the given directory is a Subversion repository. +# +rule is-repository +( + directory # A directory resulting from a fetch or checkout. +) +{ + # @todo is there a better way? + return [ path.exists "$(directory)/.svn" ] ; +} + +# Return true if the Subversion executable exists. +# +rule executable-exists ( ) +{ + # @todo always say true for now + return 1 == 1 ; +} diff --git a/src/tools/vcs.jam b/src/tools/vcs.jam new file mode 100644 index 0000000000..78f26e2f09 --- /dev/null +++ b/src/tools/vcs.jam @@ -0,0 +1,253 @@ +# Copyright 2016 +# +# Distributed under the Boost Software License, Version 1.0. (See +# accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +# Version Control System module +# +# Overview +# +# The Boost.Build ``vcs`` module exposes a limited subset of version +# control system functionality to Boost.Build projects for a set of +# supported version control system back ends. Currently, Boost.Build +# ``vcs`` supports Subversion and Git. Other systems should be +# straightforward to implement. +# +# Usage +# +# An example Boost.Build project illustrating the vcs interface is shown +# below. +# +# :: +# +# import vcs ; +# +# import assert ; +# +# # print the type of version control system and the generated +# # version string for this project +# echo [ vcs.type . ] +# echo [ vcs.generate-version-string . ] ; +# +# # fetch and checkout the 1.0 reference of a project kept in the Git +# # version control system +# vcs.get git : https://example.com/git/path/to/project/root : /path/to/desired/root ; +# vcs.checkout : /path/to/desired/root : 1.0 ; +# +# # verify that the URL and reference matches the desired +# assert.equal [ vcs.root-url /path/to/desired/root ] : https://example.com/git/path/to/desired/root ; +# assert.equal [ vcs.ref /path/to/desired/root ] : [ vcs.ref /path/to/desired/root : 1.0 ] ; +# +# Also, see the `example <../../example/vcs>`_ for an exhaustive example. + +import path ; +import errors ; +import assert ; + +debugging-enable = ; + +# The list of version control systems supported by this module. +# +supported-vcs = git svn ; + +for vcs in $(supported-vcs) +{ + import vcs-$(vcs) ; +} + +# Returns the type of version control system for the indicated +# directory, or the empty string if none was detected. +# +rule type +( + directory # A directory resulting from a fetch or checkout. +) +{ + t = ; + for vcs in $(supported-vcs) + { + if ! $(t) && [ vcs-$(vcs).is-repository $(directory) ] + { + t = $(vcs) ; + } + } + + if ! $(t) + { + errors.error "unknown vcs system at $(directory)" ; + } + + return $(t) ; +} + +# Returns a string uniquely describing the state of the repository at +# the given directory. +# +# - When on a tag, all version control systems will return the tag +# name +# +# - Otherwise +# +# - Git: ---g +# +# - Subversion: ---s +# +# The ``generate-version-string`` rule can be used to generate a version +# string for a program dynamically. The example below shows how to use +# this to create a ``version_string.cpp`` file containing the version +# string. The ``print`` module provides a mechanism to ensure that the +# generated file is only modified when the version string actually +# changes. +# +# Also, see the `example <../../example/vcs-generate-version-string>`_ for +# an complete example. +# +# :: +# +# # A Jamroot to run a program that prints a generated version string. +# +# import testing ; +# +# import vcs ; +# import print ; +# +# path-constant working-directory-root : ../.. ; +# +# # run it to see the output +# run versioned : : : : versioned-run ; +# +# # note that version_string.cpp is generated below +# exe versioned : main.cpp version_string.cpp ; +# +# # generate the version_string.cpp file +# make version_string.cpp : : @generate-file ; +# rule generate-file ( target : sources * : properties * ) +# { +# local v = [ vcs.generate-version-string $(working-directory-root) ] ; +# +# print.output $(target) ; +# print.text "const char * version_string = \"$(v)\";" : true ; +# print.text "" ; +# } +# +# :: +# +# // A program to print the version string. +# +# #include +# +# extern const char * version_string; +# +# int +# main () +# { +# std::cout << "generated version is '" << version_string << "'\n"; +# +# return 0; +# } +# +rule generate-version-string +( + directory # A directory resulting from a fetch or checkout. +) +{ + # @todo need to fix this + # if ! [ path.exists $(directory) ] + # { + # errors.user-error "$(directory) does not exist." ; + # } + + vcs = [ type $(directory) ] ; + assert.in $(vcs) : $(supported-vcs) ; + + return [ vcs-$(vcs).generate-version-string $(directory) ] ; +} + +# Fetches from the URL to the root of the vcs project to the +# indicated directory using vcs. +# +rule fetch +( + vcs : # The VCS system to use to fetch the root URL. + root-url : # The root URL of the repository from which to fetch. + directory # The directory into which to fetch the root URL. +) +{ + assert.in $(vcs) : $(supported-vcs) ; + + if [ path.exists $(directory) ] + { + assert.true type $(directory) : $(vcs) ; + + local current-url = [ vcs-$(vcs).root-url $(directory) ] ; + + if $(current-url) != $(root-url) + { + errors.error "vcs:$(vcs): $(directory) is at $(current-url) not $(root-url)" ; + } + } + else + { + if $(debugging-enable) + { + echo "vcs: fetching $(root-url) to $(directory)" ; + } + + local r1 = [ vcs-$(vcs).fetch $(root-url) : $(directory) ] ; + } +} + +# Checks out the indicated symbolic reference from the repository +# located at the indicated directory. +# +rule checkout +( + directory : # A directory resulting from a fetch or checkout. + symbolic-ref # The VCS-specific symbolic reference to check out. +) +{ + local vcs = [ type $(directory) ] ; + + assert.in $(vcs) : $(supported-vcs) ; + + if $(debugging-enable) + { + echo "vcs: checking out $(symbolic-ref) at $(directory)" ; + } + + local r = [ vcs-$(vcs).checkout $(directory) : $(symbolic-ref) ] ; +} + +# Returns the URL to the root of the vcs project located at the +# indicated directory. +# +rule root-url +( + directory # A directory resulting from a fetch or checkout. +) +{ + local vcs = [ type $(directory) ] ; + + assert.in $(vcs) : $(supported-vcs) ; + + return [ vcs-$(vcs).root-url $(directory) ] ; +} + +# Returns a unique identifier representing the current state of the +# vcs project located at directory. If the symbolic reference is +# given, the rule returns the reference of that symbolic reference, +# not the current state of the project. +# +rule ref +( + directory : # A directory resulting from a fetch or checkout. + symbolic-ref ? # An optional VCS-specific symbolic reference. +) +{ + local vcs = [ type $(directory) ] ; + + assert.in $(vcs) : $(supported-vcs) ; + + return [ vcs-$(vcs).ref $(directory) : $(symbolic-ref) ] ; +} diff --git a/test/example_vcs.py b/test/example_vcs.py new file mode 100644 index 0000000000..c33fd56838 --- /dev/null +++ b/test/example_vcs.py @@ -0,0 +1,15 @@ +#!/usr/bin/python + +# Test the 'vcs' example. + +import BoostBuild + +t = BoostBuild.Tester(use_test_config=False) + +t.set_tree("../example/vcs") + +t.run_build_system() + +#t.expect_output_lines("----------") + +t.cleanup() diff --git a/test/example_vcs_generate_version_string.py b/test/example_vcs_generate_version_string.py new file mode 100644 index 0000000000..e42bca58cc --- /dev/null +++ b/test/example_vcs_generate_version_string.py @@ -0,0 +1,15 @@ +#!/usr/bin/python + +# Test the 'vcs-generate-version-string' example. + +import BoostBuild + +t = BoostBuild.Tester(use_test_config=False) + +t.set_tree("../example/vcs-generate-version-string") + +t.run_build_system() + +#t.expect_output_lines("2016.03") + +t.cleanup() diff --git a/test/test_all.py b/test/test_all.py index 07e428159d..14bb71cc95 100644 --- a/test/test_all.py +++ b/test/test_all.py @@ -226,6 +226,8 @@ def reorder_tests(tests, first_test): "duplicate", "example_libraries", "example_make", + "example_vcs", + "example_vcs_generate_version_string", "exit_status", "expansion", "explicit", @@ -305,6 +307,7 @@ def reorder_tests(tests, first_test): "unused", "use_requirements", "using", + "vcs", "wrapper", "wrong_project", ] diff --git a/test/vcs.py b/test/vcs.py new file mode 100644 index 0000000000..c2a1323025 --- /dev/null +++ b/test/vcs.py @@ -0,0 +1,45 @@ +#!/usr/bin/python + +import os + +import BoostBuild + +t = BoostBuild.Tester(use_test_config=False) + +t.write("jamroot.jam", """ +import vcs ; + +vcs.fetch git : https://github.com/boostorg/build.git : wd-git ; + +vcs.checkout wd-git : 2016.03 ; + +echo "type:" [ vcs.type wd-git ] ; +echo "root-url:" [ vcs.root-url wd-git ] ; +echo "head-ref:" [ vcs.ref wd-git ] ; +echo "symbolic-ref:" [ vcs.ref wd-git : 2016.03 ] ; +echo "generate-version-string:" [ vcs.generate-version-string wd-git ] ; + +vcs.fetch svn : https://github.com/boostorg/build.git : wd-svn ; + +vcs.checkout wd-svn : tags/2016.03 ; + +echo "type:" [ vcs.type wd-svn ] ; +echo "root-url:" [ vcs.root-url wd-svn ] ; +echo "head-ref:" [ vcs.ref wd-svn ] ; +echo "symbolic-ref:" [ vcs.ref wd-svn : tags/2016.03 ] ; +echo "generate-version-string:" [ vcs.generate-version-string wd-svn ] ; +""") + +t.run_build_system() + +t.expect_output_lines("type: git") +t.expect_output_lines("root-url: https://github.com/boostorg/build.git") +t.expect_output_lines("head-ref: e83838da44f46bbe1f9e07c61dd8d96d13be55df") +t.expect_output_lines("symbolic-ref: e83838da44f46bbe1f9e07c61dd8d96d13be55df") +t.expect_output_lines("generate-version-string: 2016.03") + +t.expect_output_lines("type: svn") +t.expect_output_lines("root-url: https://github.com/boostorg/build.git") +t.expect_output_lines("head-ref: tags/2016.03@12703") +t.expect_output_lines("symbolic-ref: tags/2016.03@12703") +t.expect_output_lines("generate-version-string: 2016.03")