github.com/decred/politeia@v1.4.0/util/version/version.go (about)

     1  // Copyright (c) 2013-2014 The btcsuite developers
     2  // Copyright (c) 2015-2022 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  // Package version provides a single location to house the version information
     7  // for dcrd and other utilities provided in the same repository.
     8  package version
     9  
    10  import (
    11  	"fmt"
    12  	"regexp"
    13  	"strconv"
    14  	"strings"
    15  )
    16  
    17  const (
    18  	// semanticAlphabet defines the allowed characters for the pre-release and
    19  	// build metadata portions of a semantic version string.
    20  	semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-."
    21  )
    22  
    23  // semverRE is a regular expression used to parse a semantic version string
    24  // into its constituent parts.
    25  var semverRE = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` +
    26  	`(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*` +
    27  	`[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)
    28  
    29  // These variables define the application version and follow the semantic
    30  // versioning 2.0.0 spec (https://semver.org/).
    31  var (
    32  	// Note for maintainers:
    33  	//
    34  	// The expected process for setting the version in releases is as follows:
    35  	// - Create a release branch of the form 'release-vMAJOR.MINOR'
    36  	// - Modify the Version variable below on that branch to:
    37  	//   - Remove the pre-release portion
    38  	//   - Set the build metadata to 'release.local'
    39  	// - Update the Version variable below on the master branch to the next
    40  	//   expected version while retaining a pre-release of 'pre'
    41  	//
    42  	// These steps ensure that building from source produces versions that are
    43  	// distinct from reproducible builds that override the Version via linker
    44  	// flags.
    45  
    46  	// Version is the application version per the semantic versioning 2.0.0 spec
    47  	// (https://semver.org/).
    48  	//
    49  	// It is defined as a variable so it can be overridden during the build
    50  	// process with:
    51  	// '-ldflags "-X main.Version=fullsemver"'
    52  	// if needed.
    53  	//
    54  	// It MUST be a full semantic version per the semantic versioning spec or
    55  	// the app will panic at runtime.  Of particular note is the pre-release
    56  	// and build metadata portions MUST only contain characters from
    57  	// semanticAlphabet.
    58  	Version = "1.4.0-pre"
    59  
    60  	// NOTE: The following values are set via init by parsing the above Version
    61  	// string.
    62  
    63  	// These fields are the individual semantic version components that define
    64  	// the application version.
    65  	Major         uint32
    66  	Minor         uint32
    67  	Patch         uint32
    68  	PreRelease    string
    69  	BuildMetadata string
    70  )
    71  
    72  // parseUint32 converts the passed string to an unsigned integer or returns an
    73  // error if it is invalid.
    74  func parseUint32(s string, fieldName string) (uint32, error) {
    75  	val, err := strconv.ParseUint(s, 10, 32)
    76  	if err != nil {
    77  		return 0, fmt.Errorf("malformed semver %s: %w", fieldName, err)
    78  	}
    79  	return uint32(val), err
    80  }
    81  
    82  // checkSemString returns an error if the passed string contains characters that
    83  // are not in the provided alphabet.
    84  func checkSemString(s, alphabet, fieldName string) error {
    85  	for _, r := range s {
    86  		if !strings.ContainsRune(alphabet, r) {
    87  			return fmt.Errorf("malformed semver %s: %q invalid", fieldName, r)
    88  		}
    89  	}
    90  	return nil
    91  }
    92  
    93  // parseSemVer parses various semver components from the provided string.
    94  func parseSemVer(s string) (uint32, uint32, uint32, string, string, error) {
    95  	// Parse the various semver component from the version string via a regular
    96  	// expression.
    97  	m := semverRE.FindStringSubmatch(s)
    98  	if m == nil {
    99  		err := fmt.Errorf("malformed version string %q: does not conform to "+
   100  			"semver specification", s)
   101  		return 0, 0, 0, "", "", err
   102  	}
   103  
   104  	major, err := parseUint32(m[1], "major")
   105  	if err != nil {
   106  		return 0, 0, 0, "", "", err
   107  	}
   108  
   109  	minor, err := parseUint32(m[2], "minor")
   110  	if err != nil {
   111  		return 0, 0, 0, "", "", err
   112  	}
   113  
   114  	patch, err := parseUint32(m[3], "patch")
   115  	if err != nil {
   116  		return 0, 0, 0, "", "", err
   117  	}
   118  
   119  	preRel := m[4]
   120  	err = checkSemString(preRel, semanticAlphabet, "pre-release")
   121  	if err != nil {
   122  		return 0, 0, 0, "", "", err
   123  	}
   124  
   125  	build := m[5]
   126  	err = checkSemString(build, semanticAlphabet, "buildmetadata")
   127  	if err != nil {
   128  		return 0, 0, 0, "", "", err
   129  	}
   130  
   131  	return major, minor, patch, preRel, build, nil
   132  }
   133  
   134  func init() {
   135  	var err error
   136  	Major, Minor, Patch, PreRelease, BuildMetadata, err = parseSemVer(Version)
   137  	if err != nil {
   138  		panic(err)
   139  	}
   140  	if BuildMetadata == "" {
   141  		BuildMetadata = vcsCommitID()
   142  		if BuildMetadata != "" {
   143  			Version = fmt.Sprintf("%d.%d.%d", Major, Minor, Patch)
   144  			if PreRelease != "" {
   145  				Version += "-" + PreRelease
   146  			}
   147  			Version += "+" + BuildMetadata
   148  		}
   149  	}
   150  }