github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/build-aux/genversion/main.go (about)

     1  // Copyright 2021 Datawire. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"crypto/md5"
    19  	"encoding/base64"
    20  	"fmt"
    21  	"io/fs"
    22  	"os"
    23  	//nolint:depguard // This short script has no logging and no Contexts.
    24  	"os/exec"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	"github.com/blang/semver/v4"
    29  	ignore "github.com/sabhiram/go-gitignore"
    30  )
    31  
    32  // isReleased returns true if a release tag exist for the given version
    33  // A release tag is a tag that represents a semver version, without pre-version
    34  // or build suffixes, that is prefixed with "v", e.g. "v1.2.3" is considered
    35  // a release tag whereas "v1.2.3-rc.3" isn't.
    36  func isReleased(v semver.Version) bool {
    37  	v.Build = nil
    38  	v.Pre = nil
    39  	return exec.Command("git", "describe", "v"+v.String()).Run() == nil
    40  }
    41  
    42  // dirMD5 computes the MD5 checksum of all files found when recursively
    43  // traversing a directory, skipping .gitignore's, _test/, and _test.go. The
    44  // general idea is to avoid rebuilds and pushes when repeatedly running tests,
    45  // even if the tests themselves actually change.
    46  func dirMD5(root string) ([]byte, error) {
    47  	ign, err := ignore.CompileIgnoreFile(filepath.Join(root, ".gitignore"))
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	d := md5.New()
    52  	testMach := fmt.Sprintf("_test%c", filepath.Separator)
    53  	isTest := func(path string) bool {
    54  		return strings.Contains(path, testMach) || strings.HasSuffix(path, "_test.go")
    55  	}
    56  	err = filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
    57  		if err == nil && info.Mode().IsRegular() && !(ign.MatchesPath(path) || isTest(path)) {
    58  			var data []byte
    59  			if data, err = os.ReadFile(path); err == nil {
    60  				d.Write(data)
    61  			}
    62  		}
    63  		return err
    64  	})
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	return d.Sum(make([]byte, 0, md5.Size)), nil
    69  }
    70  
    71  func Main() error {
    72  	cmd := exec.Command("git", "describe", "--tags", "--match=v*")
    73  	cmd.Stderr = os.Stderr
    74  	gitDescBytes, err := cmd.Output()
    75  	if err != nil {
    76  		return fmt.Errorf("unable to git describe: %w", err)
    77  	}
    78  	gitDescStr := strings.TrimSuffix(strings.TrimPrefix(string(gitDescBytes), "v"), "\n")
    79  	gitDescVer, err := semver.Parse(gitDescStr)
    80  	if err != nil {
    81  		return fmt.Errorf("unable to parse semver %s: %w", gitDescStr, err)
    82  	}
    83  
    84  	// Bump to next patch version only if the version has been released.
    85  	if isReleased(gitDescVer) {
    86  		gitDescVer.Patch++
    87  	}
    88  
    89  	// If an additional arg has been used, we include it in the tag
    90  	if len(os.Args) >= 2 {
    91  		// gitDescVer.Pre[0] contains the number of commits since the last tag and the
    92  		// shortHash with a 'g' appended.  Since the first section isn't relevant,
    93  		// we get the shortHash this way since we don't need that extra information.
    94  		cmd = exec.Command("git", "rev-parse", "--short", "HEAD")
    95  		cmd.Stderr = os.Stderr
    96  		shortHash, err := cmd.Output()
    97  		if err != nil {
    98  			return fmt.Errorf("unable to git rev-parse: %w", err)
    99  		}
   100  		if _, err := fmt.Printf("v%d.%d.%d-%s-%s\n", gitDescVer.Major, gitDescVer.Minor, gitDescVer.Patch, os.Args[1], shortHash); err != nil {
   101  			return fmt.Errorf("unable to printf: %w", err)
   102  		}
   103  		return nil
   104  	}
   105  
   106  	// Append a mangled md5 if the directory is dirty.
   107  	cmd = exec.Command("git", "status", "--short")
   108  	cmd.Stderr = os.Stderr
   109  	statusBytes, err := cmd.Output()
   110  	if err != nil {
   111  		return fmt.Errorf("unable to git rev-parse: %w", err)
   112  	}
   113  	if len(statusBytes) > 0 {
   114  		var md5Out []byte
   115  		md5Out, err = dirMD5(".")
   116  		if err != nil {
   117  			return fmt.Errorf("unable to compute MD5: %w", err)
   118  		}
   119  
   120  		b64 := base64.RawURLEncoding.EncodeToString(md5Out)
   121  		b64 = strings.ReplaceAll(b64, "_", "Z")
   122  		b64 = strings.ReplaceAll(b64, "-", "z")
   123  		_, err = fmt.Printf("v%s-%s\n", gitDescVer, b64)
   124  	} else {
   125  		_, err = fmt.Printf("v%s\n", gitDescVer)
   126  	}
   127  	return err
   128  }
   129  
   130  func main() {
   131  	if err := Main(); err != nil {
   132  		fmt.Fprintf(os.Stderr, "%s: error: %v\n", filepath.Base(os.Args[0]), err)
   133  		os.Exit(1)
   134  	}
   135  }