pax_global_header00006660000000000000000000000064145772605750014534gustar00rootroot0000000000000052 comment=ba882c9bfa490f59921a3e7b514009ef05097e56 dh-make-golang-0.7.0/000077500000000000000000000000001457726057500143135ustar00rootroot00000000000000dh-make-golang-0.7.0/.github/000077500000000000000000000000001457726057500156535ustar00rootroot00000000000000dh-make-golang-0.7.0/.github/dependabot.yml000066400000000000000000000003251457726057500205030ustar00rootroot00000000000000# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" dh-make-golang-0.7.0/.github/workflows/000077500000000000000000000000001457726057500177105ustar00rootroot00000000000000dh-make-golang-0.7.0/.github/workflows/ci-test.yml000066400000000000000000000030111457726057500217760ustar00rootroot00000000000000name: CI Test on: push: pull_request: workflow_dispatch: jobs: ci-test: runs-on: ubuntu-22.04 if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" strategy: matrix: go-version: ['1.21', '1.22'] steps: - name: Checkout uses: actions/checkout@v4 - name: Install Go ${{ matrix.go-version }} uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} check-latest: true - run: diff -u <(echo -n) <(gofmt -d -s .) - run: go install -v ./... - run: go test -v ./... - run: go vet ./... - name: apt install needed Debian packages run: | sudo eatmydata apt update sudo eatmydata apt install git-buildpackage pristine-tar pandoc - name: Generate man page from Markdown run: pandoc -f markdown -t man -s dh-make-golang.md -o dh-make-golang.1 - name: dh-make-golang test run for "gh" run: | git config --global user.email "dh-make-golang@debian.org" git config --global user.name "dh-make-golang on GitHub" mkdir -p _test-run cd _test-run ~/go/bin/dh-make-golang -type p -pristine-tar -program_package_name gh github.com/cli/cli - name: Upload dh-make-golang test run as artifact uses: actions/upload-artifact@v4 with: name: dh-make-golang_test-run_go${{ matrix.go-version }} path: _test-run dh-make-golang-0.7.0/.gitignore000066400000000000000000000000501457726057500162760ustar00rootroot00000000000000dh-make-golang _build/ *~ *.sw[op] .ideadh-make-golang-0.7.0/LICENSE000066400000000000000000000027601457726057500153250ustar00rootroot00000000000000Copyright (c) 2015, Michael Stapelberg, Google Inc. and contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of dh-make-golang nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. dh-make-golang-0.7.0/README.md000066400000000000000000000037101457726057500155730ustar00rootroot00000000000000![CI Test](https://github.com/anthonyfok/dh-make-golang/actions/workflows/ci-test.yml/badge.svg) dh-make-golang is a tool to automatically create Debian packaging for Go packages. Its goal is to automate away as much of the work as possible when creating a Debian package for a Go library package or Go program. ## Overview All you need to specify is a Go package name. In your current working directory, a new directory will be created containing a git repository. Inside that repository, you’ll find the Go package’s source code plus the necessary Debian packaging files to build a Debian package. The packaging adheres to the [pkg-go packaging guidelines](https://go-team.pages.debian.net/packaging.html) and hence can be placed alongside the other [team-maintained packages in pkg-go](https://salsa.debian.org/go-team/packages), hosted on Debian’s [salsa](https://wiki.debian.org/Salsa). ## Usage/example For an introductory example, see [this annotated demonstration of how to use dh-make-golang](https://people.debian.org/~stapelberg/2015/07/27/dh-make-golang.html). ## dh-make-golang’s usage of the internet dh-make-golang makes heavy use of online resources to improve the resulting package. In no particular order and depending on where your package is hosted, dh-make-golang may query: * By virtue of using `go get`, the specified Go package and all of its dependencies will be downloaded. This step can quickly cause dozens of megabytes to be transferred, so be careful if you are on a metered internet connection. * The output of [filter-packages.sh](https://github.com/Debian/dh-make-golang/blob/master/filter-packages.sh), hosted on https://people.debian.org/~stapelberg/dh-make-golang/. This is used to figure out whether dependencies are already packaged in Debian, and whether you are about to duplicate somebody else’s work. * GitHub’s API, to get the license, repository creator, description and README for Go packages hosted on GitHub. dh-make-golang-0.7.0/check_depends.go000066400000000000000000000074671457726057500174370ustar00rootroot00000000000000package main import ( "fmt" "log" "os" "path/filepath" "strings" "golang.org/x/mod/modfile" "golang.org/x/tools/go/vcs" "pault.ag/go/debian/control" ) type dependency struct { importPath string packageName string // todo version? } func execCheckDepends(args []string) { cwd, err := os.Getwd() if err != nil { log.Fatalf("error while getting current directory: %s", err) } // Load the already packaged Go modules golangBinaries, err := getGolangBinaries() if err != nil { log.Fatalf("error while getting packaged Go modules: %s", err) } // Load the dependencies defined in the Go module (go.mod) goModDepds, err := parseGoModDependencies(cwd, golangBinaries) if err != nil { log.Fatalf("error while parsing go.mod: %s", err) } // Load the dependencies defined in the Debian packaging (d/control) packageDeps, err := parseDebianControlDependencies(cwd) if err != nil { log.Fatalf("error while parsing d/control: %s", err) } hasChanged := false // Check for newly introduced dependencies (defined in go.mod but not in d/control) for _, goModDep := range goModDepds { found := false if goModDep.packageName == "" { fmt.Printf("NEW dependency %s is NOT yet packaged in Debian\n", goModDep.importPath) continue } for _, packageDep := range packageDeps { if packageDep.packageName == goModDep.packageName { found = true break } } if !found { hasChanged = true fmt.Printf("NEW dependency %s (%s)\n", goModDep.importPath, goModDep.packageName) } } // Check for now unused dependencies (defined in d/control but not in go.mod) for _, packageDep := range packageDeps { found := false for _, goModDep := range goModDepds { if goModDep.packageName == packageDep.packageName { found = true break } } if !found { hasChanged = true fmt.Printf("RM dependency %s (%s)\n", packageDep.importPath, packageDep.packageName) } } if !hasChanged { fmt.Printf("go.mod and d/control are in sync\n") } } // parseGoModDependencies parse ALL dependencies listed in go.mod // i.e. it returns the one defined in go.mod as well as the transitively ones // TODO: this may not be the best way of doing thing since it requires the package to be converted to go module func parseGoModDependencies(directory string, goBinaries map[string]string) ([]dependency, error) { b, err := os.ReadFile(filepath.Join(directory, "go.mod")) if err != nil { return nil, err } modFile, err := modfile.Parse("go.mod", b, nil) if err != nil { return nil, err } var dependencies []dependency for _, require := range modFile.Require { if !require.Indirect { packageName := "" // Translate all packages to the root of their repository rr, err := vcs.RepoRootForImportPath(require.Mod.Path, false) if err != nil { log.Printf("Could not determine repo path for import path %q: %v\n", require.Mod.Path, err) continue } if val, exists := goBinaries[rr.Root]; exists { packageName = val } dependencies = append(dependencies, dependency{ importPath: rr.Root, packageName: packageName, }) } } return dependencies, nil } // parseDebianControlDependencies parse the Build-Depends defined in d/control func parseDebianControlDependencies(directory string) ([]dependency, error) { ctrl, err := control.ParseControlFile(filepath.Join(directory, "debian", "control")) if err != nil { return nil, err } var dependencies []dependency for _, bp := range ctrl.Source.BuildDepends.GetAllPossibilities() { packageName := strings.Trim(bp.Name, "\n") // Ignore non -dev dependencies (i.e, debhelper-compat, git, cmake, etc...) if !strings.HasSuffix(packageName, "-dev") { continue } dependencies = append(dependencies, dependency{ importPath: "", // TODO XS-Go-Import-Path? packageName: packageName, }) } return dependencies, nil } dh-make-golang-0.7.0/check_depends_test.go000066400000000000000000000076431457726057500204720ustar00rootroot00000000000000package main import ( "os" "path/filepath" "reflect" "testing" ) func TestParseDebianControlDependencies(t *testing.T) { f := `Source: terminews Maintainer: Debian Go Packaging Team Uploaders: Aloïs Micard , Section: news Testsuite: autopkgtest-pkg-go Priority: optional Build-Depends: debhelper-compat (= 13), dh-sequence-golang, golang-any, golang-github-advancedlogic-goose-dev, golang-github-fatih-color-dev, golang-github-jroimartin-gocui-dev, golang-github-mattn-go-sqlite3-dev, golang-github-mmcdole-gofeed-dev, Standards-Version: 4.5.1 Vcs-Browser: https://salsa.debian.org/go-team/packages/terminews Vcs-Git: https://salsa.debian.org/go-team/packages/terminews.git Homepage: https://github.com/antavelos/terminews Rules-Requires-Root: no XS-Go-Import-Path: github.com/antavelos/terminews Package: terminews Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, Built-Using: ${misc:Built-Using}, Description: read your RSS feeds from your terminal Terminews is a terminal based application (TUI) that allows you to manage RSS resources and display their news feeds. ` tmpDir, err := os.MkdirTemp("", "dh-make-golang") if err != nil { t.Fatalf("Could not create temp dir: %v", err) } defer os.RemoveAll(tmpDir) if err := os.MkdirAll(filepath.Join(tmpDir, "dummy-package", "debian"), 0750); err != nil { t.Fatalf("Could not create dummy Debian package: %v", err) } if err := os.WriteFile(filepath.Join(tmpDir, "dummy-package", "debian", "control"), []byte(f), 0640); err != nil { t.Fatalf("Could not create dummy Debian package: %v", err) } deps, err := parseDebianControlDependencies(filepath.Join(tmpDir, "dummy-package")) if err != nil { t.Fatalf("Could not parse Debian package dependencies: %v", err) } want := []dependency{ { importPath: "", packageName: "golang-github-advancedlogic-goose-dev", }, { importPath: "", packageName: "golang-github-fatih-color-dev", }, { importPath: "", packageName: "golang-github-jroimartin-gocui-dev", }, { importPath: "", packageName: "golang-github-mattn-go-sqlite3-dev", }, { importPath: "", packageName: "golang-github-mmcdole-gofeed-dev", }, } if !reflect.DeepEqual(deps, want) { t.Fatalf("Wrong dependencies returned (got %v want %v)", deps, want) } } func TestParseGoModDependencies(t *testing.T) { f := `module github.com/Debian/dh-make-golang go 1.16 require ( github.com/charmbracelet/glamour v0.3.0 github.com/google/go-github/v60 v60.0.0 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 )` tmpDir, err := os.MkdirTemp("", "dh-make-golang") if err != nil { t.Fatalf("Could not create temp dir: %v", err) } defer os.RemoveAll(tmpDir) if err := os.MkdirAll(filepath.Join(tmpDir, "dummy-package"), 0750); err != nil { t.Fatalf("Could not create dummy Debian package: %v", err) } if err := os.WriteFile(filepath.Join(tmpDir, "dummy-package", "go.mod"), []byte(f), 0640); err != nil { t.Fatalf("Could not create dummy Debian package: %v", err) } deps, err := parseGoModDependencies(filepath.Join(tmpDir, "dummy-package"), map[string]string{ "github.com/charmbracelet/glamour": "golang-github-charmbracelet-glamour-dev", "github.com/google/go-github": "golang-github-google-go-github-dev", "github.com/gregjones/httpcache": "golang-github-gregjones-httpcache-dev", }) if err != nil { t.Fatalf("Could not parse go.mod dependencies: %v", err) } want := []dependency{ { importPath: "github.com/charmbracelet/glamour", packageName: "golang-github-charmbracelet-glamour-dev", }, { importPath: "github.com/google/go-github", packageName: "golang-github-google-go-github-dev", }, { importPath: "github.com/gregjones/httpcache", packageName: "golang-github-gregjones-httpcache-dev", }, } if !reflect.DeepEqual(deps, want) { t.Fatalf("Wrong dependencies returned (got %v want %v)", deps, want) } } dh-make-golang-0.7.0/clone.go000066400000000000000000000015061457726057500157440ustar00rootroot00000000000000package main import ( "flag" "fmt" "log" "os" "os/exec" ) func execClone(args []string) { fs := flag.NewFlagSet("clone", flag.ExitOnError) fs.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s clone \n", os.Args[0]) fmt.Fprintf(os.Stderr, "Clone a Go package from Salsa\n"+ "and download the appropriate tarball.\n") fmt.Fprintf(os.Stderr, "Example: %s clone golang-github-mmcdole-goxpp\n", os.Args[0]) } err := fs.Parse(args) if err != nil { log.Fatalf("parse args: %s", err) } if fs.NArg() != 1 { fs.Usage() os.Exit(1) } cmd := exec.Command("gbp", "clone", fmt.Sprintf("vcsgit:%s", fs.Arg(0)), "--postclone=origtargz") cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { log.Fatalf("Could not run %v: %v", cmd.Args, err) } fmt.Printf("Successfully cloned %s\n", fs.Arg(0)) } dh-make-golang-0.7.0/create_salsa_project.go000066400000000000000000000022061457726057500210160ustar00rootroot00000000000000package main import ( "flag" "fmt" "io" "log" "net/http" "net/url" "os" ) func execCreateSalsaProject(args []string) { fs := flag.NewFlagSet("create-salsa-project", flag.ExitOnError) fs.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s create-salsa-project \n", os.Args[0]) fmt.Fprintf(os.Stderr, "Example: %s create-salsa-project golang-github-mattn-go-sqlite3\n", os.Args[0]) } if err := fs.Parse(args); err != nil { log.Fatalf("parse: %s", err) } if fs.NArg() != 1 { fs.Usage() os.Exit(1) } projectName := fs.Arg(0) // The source code of the corresponding server can be found at: // https://salsa.debian.org/go-team/infra/pkg-go-tools/-/tree/master/cmd/pgt-api-server u, _ := url.Parse("https://pgt-api-server.debian.net/v1/createrepo") q := u.Query() q.Set("repo", projectName) u.RawQuery = q.Encode() resp, err := http.Post(u.String(), "", nil) if err != nil { log.Fatalf("http post: %s", err) } if got, want := resp.StatusCode, http.StatusOK; got != want { b, _ := io.ReadAll(resp.Body) log.Fatalf("unexpected HTTP status code: got %d, want %d (response: %s)", got, want, string(b)) } } dh-make-golang-0.7.0/description.go000066400000000000000000000054541457726057500171750ustar00rootroot00000000000000package main import ( "context" _ "embed" "fmt" "regexp" "strings" "github.com/charmbracelet/glamour" ) //go:embed description.json var descriptionJSONBytes []byte // reformatForControl reformats the wrapped description // to conform to Debian’s control format. func reformatForControl(raw string) string { output := "" next_prefix := "" re := regexp.MustCompile(`^ \d+\. `) for _, line := range strings.Split(strings.TrimSpace(raw), "\n") { // Remove paddings that Glamour currently add to the end of each line line = strings.TrimRight(line, " ") // Try to add hanging indent for list items that span over one line prefix := next_prefix if strings.HasPrefix(line, " * ") { // unordered list prefix = "" next_prefix = " " } if re.MatchString(line) { // ordered list prefix = "" next_prefix = " " } if line == "" { // blank line, implying end of list line = "." prefix = "" next_prefix = "" } output += " " + prefix + line + "\n" } return output } // markdownToLongDescription converts Markdown to plain text // and reformat it for expanded description in debian/control. func markdownToLongDescription(markdown string) (string, error) { r, _ := glamour.NewTermRenderer( glamour.WithStylesFromJSONBytes(descriptionJSONBytes), glamour.WithWordWrap(72), ) out, err := r.Render(markdown) if err != nil { return "", fmt.Errorf("fail to render Markdown: %w", err) } //fmt.Println(out) //fmt.Println(reformatForControl(out)) return reformatForControl(out), nil } // getDescriptionForGopkg reads from README.md (or equivalent) from GitHub, // intended for extended description in debian/control. func getLongDescriptionForGopkg(gopkg string) (string, error) { owner, repo, err := findGitHubRepo(gopkg) if err != nil { return "", fmt.Errorf("find github repo: %w", err) } rr, _, err := gitHub.Repositories.GetReadme(context.TODO(), owner, repo, nil) if err != nil { return "", fmt.Errorf("get readme: %w", err) } content, err := rr.GetContent() if err != nil { return "", fmt.Errorf("get content: %w", err) } // Supported filename suffixes are from // https://github.com/github/markup/blob/master/README.md // NOTE(stapelberg): Ideally, we’d use https://github.com/github/markup // itself to render to HTML, then convert HTML to plaintext. That sounds // fairly involved, but it’d be the most correct solution to the problem at // hand. Our current code just knows markdown, which is good enough since // most (Go?) projects in fact use markdown for their README files. if !strings.HasSuffix(rr.GetName(), "md") && !strings.HasSuffix(rr.GetName(), "markdown") && !strings.HasSuffix(rr.GetName(), "mdown") && !strings.HasSuffix(rr.GetName(), "mkdn") { return reformatForControl(content), nil } return markdownToLongDescription(content) } dh-make-golang-0.7.0/description.json000066400000000000000000000027761457726057500175450ustar00rootroot00000000000000{ "document": { "block_prefix": "\n", "block_suffix": "\n", "margin": 0 }, "block_quote": { "indent": 1, "indent_token": " | " }, "paragraph": {}, "list": { "indent": 1, "level_indent": 4 }, "heading": { "block_suffix": "\n" }, "h1": { "prefix": "" }, "h2": { "prefix": "" }, "h3": { "prefix": "" }, "h4": { "prefix": "" }, "h5": { "prefix": "" }, "h6": { "prefix": "" }, "text": {}, "strikethrough": { "block_prefix": "~~", "block_suffix": "~~" }, "emph": { "block_prefix": "*", "block_suffix": "*" }, "strong": { "block_prefix": "**", "block_suffix": "**" }, "hr": { "format": "\n------------------------------------------------------------------------\n" }, "item": { "block_prefix": "* ", "indent": 2 }, "enumeration": { "block_prefix": ". ", "indent": 2 }, "task": { "ticked": "[x] ", "unticked": "[ ] " }, "link": { "prefix": "(", "suffix": ")" }, "link_text": {}, "image": { "prefix": "(", "suffix": ")" }, "image_text": { "format": "[Image: {{.text}}]" }, "code": { "block_prefix": "", "block_suffix": "" }, "code_block": { "margin": 2 }, "table": { "center_separator": "+", "column_separator": "|", "row_separator": "-" }, "definition_list": {}, "definition_term": {}, "definition_description": { "block_prefix": "\n* " }, "html_block": {}, "html_span": {} } dh-make-golang-0.7.0/description_test.go000066400000000000000000000106451457726057500202320ustar00rootroot00000000000000package main import ( "strings" "testing" ) func TestReformatForControl(t *testing.T) { content := ` There are a few options for using a custom style: 1. Call glamour.Render(inputText, "desiredStyle") 2. Set the GLAMOUR_STYLE environment variable to your desired default style or a file location for a style and call glamour.RenderWithEnvironmentConfig(inputText) 3. Set the GLAMOUR_STYLE environment variable and pass glamour.WithEnvironmentConfig() to your custom renderer Check out these projects, which use glamour: * Glow (https://github.com/charmbracelet/glow), a markdown renderer for the command-line. * GitHub CLI (https://github.com/cli/cli), GitHub’s official command line tool. * GLab (https://github.com/profclems/glab), An open source GitLab command line tool. ` want := ` There are a few options for using a custom style: . 1. Call glamour.Render(inputText, "desiredStyle") 2. Set the GLAMOUR_STYLE environment variable to your desired default style or a file location for a style and call glamour.RenderWithEnvironmentConfig(inputText) 3. Set the GLAMOUR_STYLE environment variable and pass glamour.WithEnvironmentConfig() to your custom renderer . Check out these projects, which use glamour: . * Glow (https://github.com/charmbracelet/glow), a markdown renderer for the command-line. * GitHub CLI (https://github.com/cli/cli), GitHub’s official command line tool. * GLab (https://github.com/profclems/glab), An open source GitLab command line tool. ` got := reformatForControl(content) if got != want { t.Errorf("\nwant\n====\n%v\ngot\n===\n%v", want, got) } } func TestMarkdownToLongDescription(t *testing.T) { content := ` ## Styles You can find all available default styles in our [gallery](https://github.com/charmbracelet/glamour/tree/master/styles/gallery). Want to create your own style? [Learn how!](https://github.com/charmbracelet/glamour/tree/master/styles) There are a few options for using a custom style: 1. Call §glamour.Render(inputText, "desiredStyle")§ 1. Set the §GLAMOUR_STYLE§ environment variable to your desired default style or a file location for a style and call §glamour.RenderWithEnvironmentConfig(inputText)§ 1. Set the §GLAMOUR_STYLE§ environment variable and pass §glamour.WithEnvironmentConfig()§ to your custom renderer ## Glamourous Projects Check out these projects, which use §glamour§: - [Glow](https://github.com/charmbracelet/glow), a markdown renderer for the command-line. - [GitHub CLI](https://github.com/cli/cli), GitHub’s official command line tool. - [GLab](https://github.com/profclems/glab), An open source GitLab command line tool. ` content = strings.Replace(content, "§", "`", -1) want := ` Styles . You can find all available default styles in our gallery (https://github.com/charmbracelet/glamour/tree/master/styles/gallery). Want to create your own style? Learn how! (https://github.com/charmbracelet/glamour/tree/master/styles) . There are a few options for using a custom style: . 1. Call glamour.Render(inputText, "desiredStyle") 2. Set the GLAMOUR_STYLE environment variable to your desired default style or a file location for a style and call glamour.RenderWithEnvironmentConfig(inputText) 3. Set the GLAMOUR_STYLE environment variable and pass glamour.WithEnvironmentConfig() to your custom renderer . Glamourous Projects . Check out these projects, which use glamour: . * Glow (https://github.com/charmbracelet/glow), a markdown renderer for the command-line. * GitHub CLI (https://github.com/cli/cli), GitHub’s official command line tool. * GLab (https://github.com/profclems/glab), An open source GitLab command line tool. ` got, err := markdownToLongDescription(content) if err != nil { t.Errorf("markdownToLongDescription failed: %v", err) } if got != want { t.Errorf("\nwant\n====\n%v\ngot\n===\n%v", want, got) } } dh-make-golang-0.7.0/dh-make-golang.md000066400000000000000000000027521457726057500174160ustar00rootroot00000000000000% DH-MAKE-GOLANG(1) 2018-09-15 # NAME dh-make-golang - automatically creates Debian packaging for Go packages # SYNOPSIS **dh-make-golang** [*globalflags*] <*command*> [*flags*] <*args*> # DESCRIPTION **dh-make-golang** is a tool to automatically create Debian packaging for Go packages. Its goal is to automate away as much of the work as possible when creating a Debian package for a Go library package. For backwards compatibility, when no command is specified, the **make** command is executed. To learn more about a command, run "dh-make-golang <*command*> -help", for example "dh-make-golang make -help". # COMMANDS **make** *go-package-importpath* : Create a Debian package. **dh-make-golang** will create new files and directories in the current working directory. It will connect to the internet to download the specified Go package. **search** *pattern* : Search Debian for already-existing packages. Uses Go's default regexp syntax (https://golang.org/pkg/regexp/syntax/). **estimate** *go-package-importpath* : Estimates the work necessary to bring *go-package-importpath* into Debian by printing all currently unpacked repositories. **create-salsa-project** *project-name* : Create a project for hosting Debian packaging. # OPTIONS Run **dh-make-golang** -help for more details. # AUTHOR This manual page was written by Michael Stapelberg and Dr.\ Tobias Quathamer , for the Debian project (and may be used by others). dh-make-golang-0.7.0/estimate.go000066400000000000000000000117611457726057500164630ustar00rootroot00000000000000package main import ( "flag" "fmt" "go/build" "log" "os" "os/exec" "path/filepath" "sort" "strings" "golang.org/x/tools/go/vcs" "golang.org/x/tools/refactor/importgraph" ) func get(gopath, repo string) error { done := make(chan struct{}) defer close(done) go progressSize("go get", filepath.Join(gopath, "src"), done) // As per https://groups.google.com/forum/#!topic/golang-nuts/N5apfenE4m4, // the arguments to “go get” are packages, not repositories. Hence, we // specify “gopkg/...” in order to cover all packages. // As a concrete example, github.com/jacobsa/util is a repository we want // to package into a single Debian package, and using “go get -d // github.com/jacobsa/util” fails because there are no buildable go files // in the top level of that repository. cmd := exec.Command("go", "get", "-d", "-t", repo+"/...") cmd.Stderr = os.Stderr cmd.Env = append([]string{ "GO111MODULE=off", "GOPATH=" + gopath, }, passthroughEnv()...) return cmd.Run() } func removeVendor(gopath string) (found bool, _ error) { err := filepath.Walk(filepath.Join(gopath, "src"), func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { return nil // skip non-directories } if info.Name() != "vendor" { return nil } found = true if err := os.RemoveAll(path); err != nil { return fmt.Errorf("remove all: %w", err) } return filepath.SkipDir }) return found, err } func estimate(importpath string) error { // construct a separate GOPATH in a temporary directory gopath, err := os.MkdirTemp("", "dh-make-golang") if err != nil { return fmt.Errorf("create temp dir: %w", err) } defer os.RemoveAll(gopath) if err := get(gopath, importpath); err != nil { return fmt.Errorf("go get: %w", err) } found, err := removeVendor(gopath) if err != nil { return fmt.Errorf("remove vendor: %w", err) } if found { // Fetch un-vendored dependencies if err := get(gopath, importpath); err != nil { return fmt.Errorf("fetch un-vendored: go get: %w", err) } } // Remove standard lib packages cmd := exec.Command("go", "list", "std") cmd.Stderr = os.Stderr cmd.Env = append([]string{ "GO111MODULE=off", "GOPATH=" + gopath, }, passthroughEnv()...) out, err := cmd.Output() if err != nil { return fmt.Errorf("go list std: args: %v; error: %w", cmd.Args, err) } stdlib := make(map[string]bool) for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { stdlib[line] = true } stdlib["C"] = true // would fail resolving anyway // Filter out all already-packaged ones: golangBinaries, err := getGolangBinaries() if err != nil { return nil } build.Default.GOPATH = gopath forward, _, errors := importgraph.Build(&build.Default) if len(errors) > 0 { lines := make([]string, 0, len(errors)) for importPath, err := range errors { lines = append(lines, fmt.Sprintf("%s: %v", importPath, err)) } return fmt.Errorf("could not load packages: %v", strings.Join(lines, "\n")) } var lines []string seen := make(map[string]bool) rrseen := make(map[string]bool) node := func(importPath string, indent int) { rr, err := vcs.RepoRootForImportPath(importPath, false) if err != nil { log.Printf("Could not determine repo path for import path %q: %v\n", importPath, err) return } if rrseen[rr.Root] { return } rrseen[rr.Root] = true if _, ok := golangBinaries[rr.Root]; ok { return // already packaged in Debian } lines = append(lines, fmt.Sprintf("%s%s", strings.Repeat(" ", indent), rr.Root)) } var visit func(x string, indent int) visit = func(x string, indent int) { if seen[x] { return } seen[x] = true if !stdlib[x] { node(x, indent) } for y := range forward[x] { visit(y, indent+1) } } keys := make([]string, 0, len(forward)) for key := range forward { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { if !strings.HasPrefix(key, importpath) { continue } if seen[key] { continue // already covered in a previous visit call } visit(key, 0) } if len(lines) == 0 { log.Printf("%s is already fully packaged in Debian", importpath) return nil } log.Printf("Bringing %s to Debian requires packaging the following Go packages:", importpath) for _, line := range lines { fmt.Println(line) } return nil } func execEstimate(args []string) { fs := flag.NewFlagSet("estimate", flag.ExitOnError) fs.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s estimate \n", os.Args[0]) fmt.Fprintf(os.Stderr, "Estimates the work necessary to bring into Debian\n"+ "by printing all currently unpacked repositories.\n") fmt.Fprintf(os.Stderr, "Example: %s estimate github.com/Debian/dh-make-golang\n", os.Args[0]) } err := fs.Parse(args) if err != nil { log.Fatalf("parse args: %s", err) } if fs.NArg() != 1 { fs.Usage() os.Exit(1) } // TODO: support the -git_revision flag if err := estimate(fs.Arg(0)); err != nil { log.Fatalf("estimate: %s", err) } } dh-make-golang-0.7.0/filter-packages.sh000077500000000000000000000006101457726057500177100ustar00rootroot00000000000000#!/bin/zsh mkdir -p /home/stapelberg/public_html/dh-make-golang TEMPFILE=$(mktemp) wget -q -O- http://httpredir.debian.org/debian/dists/sid/main/binary-amd64/Packages.gz | zgrep '^Package: golang-' | uniq > "${TEMPFILE}" if [ -s "${TEMPFILE}" ]; then chmod o+r "${TEMPFILE}" mv "${TEMPFILE}" /home/stapelberg/public_html/dh-make-golang/binary-amd64-grep-golang else rm "${TEMPFILE}" fi dh-make-golang-0.7.0/go.mod000066400000000000000000000025171457726057500154260ustar00rootroot00000000000000module github.com/Debian/dh-make-golang go 1.21 toolchain go1.22.1 require ( github.com/charmbracelet/glamour v0.3.1-0.20211129115518-6db32e77e152 github.com/google/go-github/v60 v60.0.0 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 github.com/mattn/go-isatty v0.0.14 golang.org/x/mod v0.15.0 golang.org/x/net v0.21.0 golang.org/x/sync v0.6.0 golang.org/x/sys v0.17.0 golang.org/x/tools v0.13.0 golang.org/x/tools/go/vcs v0.1.0-deprecated pault.ag/go/debian v0.12.0 ) require ( github.com/alecthomas/chroma v0.9.4 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/microcosm-cc/bluemonday v1.0.16 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.9.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/stretchr/testify v1.8.1 // indirect github.com/yuin/goldmark v1.4.13 // indirect github.com/yuin/goldmark-emoji v1.0.1 // indirect golang.org/x/crypto v0.19.0 // indirect pault.ag/go/topsort v0.0.0-20160530003732-f98d2ad46e1a // indirect ) dh-make-golang-0.7.0/go.sum000066400000000000000000000213521457726057500154510ustar00rootroot00000000000000github.com/DataDog/zstd v1.4.8/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/alecthomas/chroma v0.9.4 h1:YL7sOAE3p8HS96T9km7RgvmsZIctqbK1qJ0b7hzed44= github.com/alecthomas/chroma v0.9.4/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/charmbracelet/glamour v0.3.1-0.20211129115518-6db32e77e152 h1:K2PSzVHtA21y6pf2fb3U3XW9rfJr3l8Kh3j8tW2OubM= github.com/charmbracelet/glamour v0.3.1-0.20211129115518-6db32e77e152/go.mod h1:zLTCxfzlfp5uUZmUYefEzju3C+QLX9VrWR839k0jDj0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v60 v60.0.0 h1:oLG98PsLauFvvu4D/YPxq374jhSxFYdzQGNCyONLfn8= github.com/google/go-github/v60 v60.0.0/go.mod h1:ByhX2dP9XT9o/ll2yXAu2VD8l5eNVg8hD4Cr0S/LmQk= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/microcosm-cc/bluemonday v1.0.16 h1:kHmAq2t7WPWLjiGvzKa5o3HzSfahUKiOq7fAPUiMNIc= github.com/microcosm-cc/bluemonday v1.0.16/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8= github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools/go/vcs v0.1.0-deprecated h1:cOIJqWBl99H1dH5LWizPa+0ImeeJq3t3cJjaeOWUAL4= golang.org/x/tools/go/vcs v0.1.0-deprecated/go.mod h1:zUrvATBAvEI9535oC0yWYsLsHIV4Z7g63sNPVMtuBy8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= pault.ag/go/debian v0.12.0 h1:b8ctSdBSGJ98NE1VLn06aSx70EUpczlP2qqSHEiYYJA= pault.ag/go/debian v0.12.0/go.mod h1:UbnMr3z/KZepjq7VzbYgBEfz8j4+Pyrm2L5X1fzhy/k= pault.ag/go/topsort v0.0.0-20160530003732-f98d2ad46e1a h1:WwS7vlB5H2AtwKj1jsGwp2ZLud1x6WXRXh2fXsRqrcA= pault.ag/go/topsort v0.0.0-20160530003732-f98d2ad46e1a/go.mod h1:INqx0ClF7kmPAMk2zVTX8DRnhZ/yaA/Mg52g8KFKE7k= dh-make-golang-0.7.0/main.go000066400000000000000000000043551457726057500155750ustar00rootroot00000000000000package main import ( "fmt" "os" "github.com/google/go-github/v60/github" "github.com/gregjones/httpcache" ) const program = "dh-make-golang" var ( gitHub *github.Client ) func usage() { fmt.Fprintf(os.Stderr, "%s\n", buildVersionString()) fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "%s is a tool that converts Go packages into Debian package source.\n", program) fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "Usage:\n\t%s [globalflags] [flags] \n", program) fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "%s commands:\n", program) fmt.Fprintf(os.Stderr, "\tmake\t\t\tcreate a Debian package\n") fmt.Fprintf(os.Stderr, "\tsearch\t\t\tsearch Debian for already-existing packages\n") fmt.Fprintf(os.Stderr, "\testimate\t\testimate the amount of work for a package\n") fmt.Fprintf(os.Stderr, "\tcreate-salsa-project\tcreate a project for hosting Debian packaging\n") fmt.Fprintf(os.Stderr, "\tclone\t\t\tclone a Go package from Salsa\n") fmt.Fprintf(os.Stderr, "\tcheck-depends\t\tcompare go.mod and d/control to check for changes\n") fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "For backwards compatibility, when no command is specified,\nthe make command is executed.\n") fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "To learn more about a command, run \"%s -help\",\ne.g. \"%s make -help\"\n", program, program) fmt.Fprintf(os.Stderr, "\n") } func main() { transport := github.BasicAuthTransport{ Username: os.Getenv("GITHUB_USERNAME"), Password: os.Getenv("GITHUB_PASSWORD"), OTP: os.Getenv("GITHUB_OTP"), Transport: httpcache.NewMemoryCacheTransport(), } gitHub = github.NewClient(transport.Client()) // Retrieve args and Shift binary name off argument list. args := os.Args[1:] // Retrieve command name as first argument. cmd := "" if len(args) > 0 { cmd = args[0] } switch cmd { case "help": usage() case "search": execSearch(args[1:]) case "create-salsa-project": execCreateSalsaProject(args[1:]) case "estimate": execEstimate(args[1:]) case "make": execMake(args[1:], nil) case "clone": execClone(args[1:]) case "check-depends": execCheckDepends(args[1:]) default: // redirect -help to the global usage execMake(args, usage) } } dh-make-golang-0.7.0/make.go000066400000000000000000001032601457726057500155610ustar00rootroot00000000000000package main import ( "errors" "flag" "fmt" "io" "log" "net/http" "net/url" "os" "os/exec" "os/user" "path/filepath" "strings" "golang.org/x/net/publicsuffix" "golang.org/x/sync/errgroup" "golang.org/x/tools/go/vcs" ) type packageType int const ( typeGuess packageType = iota typeLibrary typeProgram typeLibraryProgram typeProgramLibrary ) var wrapAndSort string var errUnsupportedHoster = errors.New("unsupported hoster") func passthroughEnv() []string { var relevantVariables = []string{ "HOME", "PATH", "HTTP_PROXY", "http_proxy", "HTTPS_PROXY", "https_proxy", "ALL_PROXY", "all_proxy", "NO_PROXY", "no_proxy", "GIT_PROXY_COMMAND", "GIT_HTTP_PROXY_AUTHMETHOD", } var result []string for _, variable := range relevantVariables { if value, ok := os.LookupEnv(variable); ok { result = append(result, fmt.Sprintf("%s=%s", variable, value)) } } return result } func findVendorDirs(dir string) ([]string, error) { var vendorDirs []string err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info != nil && !info.IsDir() { return nil // nothing to do for anything but directories } if info.Name() == ".git" || info.Name() == ".hg" || info.Name() == ".bzr" { return filepath.SkipDir } if info.Name() == "vendor" { rel, err := filepath.Rel(dir, path) if err != nil { return fmt.Errorf("filepath.Rel: %w", err) } vendorDirs = append(vendorDirs, rel) } return nil }) return vendorDirs, err } func downloadFile(filename, url string) error { dst, err := os.Create(filename) if err != nil { return fmt.Errorf("create: %w", err) } defer dst.Close() resp, err := http.Get(url) if err != nil { return fmt.Errorf("http get: %w", err) } defer resp.Body.Close() if resp.StatusCode != 200 { return fmt.Errorf("response: %s", resp.Status) } _, err = io.Copy(dst, resp.Body) if err != nil { return fmt.Errorf("copy: %w", err) } return nil } // upstream describes the upstream repo we are about to package. type upstream struct { rr *vcs.RepoRoot tarPath string // path to the downloaded or generated orig tarball tempfile compression string // compression method, either "gz" or "xz" version string // Debian package upstream version number, e.g. 0.0~git20180204.1d24609 tag string // Latest upstream tag, if any commitIsh string // commit-ish corresponding to upstream version to be packaged remote string // git remote, set to short hostname if upstream git history is included firstMain string // import path of the first main package within repo, if any vendorDirs []string // all vendor sub directories, relative to the repo directory repoDeps []string // the repository paths of all dependencies (e.g. github.com/zyedidia/glob) hasGodeps bool // whether the Godeps/_workspace directory exists hasRelease bool // whether any release tags exist, for debian/watch isRelease bool // whether what we end up packaging is a tagged release } func (u *upstream) get(gopath, repo, rev string) error { done := make(chan struct{}) defer close(done) go progressSize("go get", filepath.Join(gopath, "src"), done) rr, err := vcs.RepoRootForImportPath(repo, false) if err != nil { return fmt.Errorf("get repo root: %w", err) } u.rr = rr dir := filepath.Join(gopath, "src", rr.Root) if rev != "" { // Run "git clone {repo} {dir}" and "git checkout {tag}" return rr.VCS.CreateAtRev(dir, rr.Repo, rev) } // Run "git clone {repo} {dir}" (or the equivalent command for hg, svn, bzr) return rr.VCS.Create(dir, rr.Repo) } func (u *upstream) tarballUrl() (string, error) { repo := strings.TrimSuffix(u.rr.Repo, ".git") repoU, err := url.Parse(repo) if err != nil { return "", fmt.Errorf("parse URL: %w", err) } switch repoU.Host { case "github.com": return fmt.Sprintf("%s/archive/%s.tar.%s", repo, u.tag, u.compression), nil case "gitlab.com", "salsa.debian.org": parts := strings.Split(repoU.Path, "/") if len(parts) < 3 { return "", fmt.Errorf("incomplete repo URL: %s", u.rr.Repo) } project := parts[2] return fmt.Sprintf("%s/-/archive/%s/%s-%s.tar.%s", repo, u.tag, project, u.tag, u.compression), nil case "git.sr.ht": return fmt.Sprintf("%s/archive/%s.tar.%s", repo, u.tag, u.compression), nil default: return "", errUnsupportedHoster } } func (u *upstream) tarballFromHoster() error { tarURL, err := u.tarballUrl() if err != nil { return err } done := make(chan struct{}) go progressSize("Download", u.tarPath, done) log.Printf("Downloading %s", tarURL) err = downloadFile(u.tarPath, tarURL) close(done) return err } func (u *upstream) tar(gopath, repo string) error { f, err := os.CreateTemp("", "dh-make-golang") if err != nil { return fmt.Errorf("create temp file: %w", err) } u.tarPath = f.Name() f.Close() if u.isRelease { if u.hasGodeps { log.Printf("Godeps/_workspace exists, not downloading tarball from hoster.") } else { u.compression = "gz" if err := u.tarballFromHoster(); err == nil { return nil } else if err == errUnsupportedHoster { log.Printf("INFO: Hoster does not provide release tarball\n") } else { return fmt.Errorf("tarball from hoster: %w", err) } } } u.compression = "xz" base := filepath.Base(repo) log.Printf("Generating temp tarball as %q\n", u.tarPath) dir := filepath.Dir(repo) cmd := exec.Command( "tar", "cJf", u.tarPath, "--exclude=.git", "--exclude=Godeps/_workspace", "--exclude="+base+"/debian", base) cmd.Dir = filepath.Join(gopath, "src", dir) cmd.Stderr = os.Stderr return cmd.Run() } // findMains finds main packages within the repo (useful to auto-detect the // package type). func (u *upstream) findMains(gopath, repo string) error { cmd := exec.Command("go", "list", "-e", "-f", "{{.ImportPath}} {{.Name}}", repo+"/...") cmd.Dir = filepath.Join(gopath, "src", repo) cmd.Env = passthroughEnv() cmd.Stderr = os.Stderr log.Println("findMains: Running", cmd, "in", cmd.Dir) out, err := cmd.Output() if err != nil { log.Println("WARNING: In findMains:", fmt.Errorf("%q: %w", cmd.Args, err)) // See https://bugs.debian.org/992610 log.Printf("Retrying without appending \"/...\" to repo") cmd = exec.Command("go", "list", "-e", "-f", "{{.ImportPath}} {{.Name}}", repo) cmd.Dir = filepath.Join(gopath, "src", repo) cmd.Env = passthroughEnv() cmd.Stderr = os.Stderr log.Println("findMains: Running", cmd, "in", cmd.Dir) out, err = cmd.Output() if err != nil { log.Println("WARNING: In findMains:", fmt.Errorf("%q: %w", cmd.Args, err)) } } for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { if strings.Contains(line, "/vendor/") || strings.Contains(line, "/Godeps/") || strings.Contains(line, "/samples/") || strings.Contains(line, "/examples/") || strings.Contains(line, "/example/") { continue } if strings.HasSuffix(line, " main") { u.firstMain = strings.TrimSuffix(line, " main") break } } return nil } func (u *upstream) findDependencies(gopath, repo string) error { log.Printf("Determining dependencies\n") cmd := exec.Command("go", "list", "-e", "-f", "{{join .Imports \"\\n\"}}\n{{join .TestImports \"\\n\"}}\n{{join .XTestImports \"\\n\"}}", repo+"/...") cmd.Dir = filepath.Join(gopath, "src", repo) cmd.Env = passthroughEnv() cmd.Stderr = os.Stderr out, err := cmd.Output() if err != nil { log.Println("WARNING: In findDependencies:", fmt.Errorf("%q: %w", cmd.Args, err)) // See https://bugs.debian.org/992610 log.Printf("Retrying without appending \"/...\" to repo") cmd = exec.Command("go", "list", "-e", "-f", "{{join .Imports \"\\n\"}}\n{{join .TestImports \"\\n\"}}\n{{join .XTestImports \"\\n\"}}", repo) cmd.Dir = filepath.Join(gopath, "src", repo) cmd.Env = passthroughEnv() cmd.Stderr = os.Stderr out, err = cmd.Output() if err != nil { log.Println("WARNING: In findDependencies:", fmt.Errorf("%q: %w", cmd.Args, err)) } } godependencies := make(map[string]bool) for _, p := range strings.Split(strings.TrimSpace(string(out)), "\n") { if p == "" { continue // skip separators between import types } // Strip packages that are included in the repository we are packaging. if strings.HasPrefix(p, repo+"/") || p == repo { continue } if p == "C" { // TODO: maybe parse the comments to figure out C deps from pkg-config files? } else { godependencies[p] = true } } if len(godependencies) == 0 { return nil } // Remove all packages which are in the standard lib. cmd = exec.Command("go", "list", "std") cmd.Stderr = os.Stderr cmd.Env = passthroughEnv() out, err = cmd.Output() if err != nil { return fmt.Errorf("go list std: (args: %v): %w", cmd.Args, err) } for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { delete(godependencies, line) } // Resolve all packages to the root of their repository. roots := make(map[string]bool) for dep := range godependencies { rr, err := vcs.RepoRootForImportPath(dep, false) if err != nil { log.Printf("Could not determine repo path for import path %q: %v\n", dep, err) continue } roots[rr.Root] = true } u.repoDeps = make([]string, 0, len(godependencies)) for root := range roots { u.repoDeps = append(u.repoDeps, root) } return nil } func makeUpstreamSourceTarball(repo, revision string, forcePrerelease bool) (*upstream, error) { gopath, err := os.MkdirTemp("", "dh-make-golang") if err != nil { return nil, fmt.Errorf("create tmp dir: %w", err) } defer os.RemoveAll(gopath) repoDir := filepath.Join(gopath, "src", repo) var u upstream log.Printf("Downloading %q\n", repo+"/...") if err := u.get(gopath, repo, revision); err != nil { return nil, fmt.Errorf("go get: %w", err) } // Verify early this repository uses git (we call pkgVersionFromGit later): if _, err := os.Stat(filepath.Join(repoDir, ".git")); os.IsNotExist(err) { return nil, fmt.Errorf("not a git repository; dh-make-golang currently only supports git") } if _, err := os.Stat(filepath.Join(repoDir, "debian")); err == nil { log.Printf("WARNING: ignoring debian/ directory that came with the upstream sources\n") } u.vendorDirs, err = findVendorDirs(repoDir) if err != nil { return nil, fmt.Errorf("find vendor dirs: %w", err) } if len(u.vendorDirs) > 0 { log.Printf("Deleting upstream vendor/ directories") for _, dir := range u.vendorDirs { if err := os.RemoveAll(filepath.Join(repoDir, dir)); err != nil { return nil, fmt.Errorf("remove all: %w", err) } } } if _, err := os.Stat(filepath.Join(repoDir, "Godeps", "_workspace")); !os.IsNotExist(err) { log.Println("Godeps/_workspace detected") u.hasGodeps = true } log.Printf("Determining upstream version number\n") u.version, err = pkgVersionFromGit(repoDir, &u, forcePrerelease) if err != nil { return nil, fmt.Errorf("get package version from Git: %w", err) } log.Printf("Package version is %q\n", u.version) if err := u.findMains(gopath, repo); err != nil { return nil, fmt.Errorf("find mains: %w", err) } if err := u.findDependencies(gopath, repo); err != nil { return nil, fmt.Errorf("find dependencies: %w", err) } if err := u.tar(gopath, repo); err != nil { return nil, fmt.Errorf("tar: %w", err) } return &u, nil } func runGitCommandIn(dir string, arg ...string) error { cmd := exec.Command("git", arg...) cmd.Dir = dir cmd.Stderr = os.Stderr return cmd.Run() } func createGitRepository(debsrc, gopkg, orig string, u *upstream, includeUpstreamHistory bool, allowUnknownHoster bool, debianBranch string, pristineTar bool) (string, error) { wd, err := os.Getwd() if err != nil { return "", fmt.Errorf("get cwd: %w", err) } dir := filepath.Join(wd, debsrc) if err := os.Mkdir(dir, 0755); err != nil { return "", fmt.Errorf("mkdir: %w", err) } // "git init -b" is the one-liner we need here, however it was added in Git 2.28. // For now we prefer to keep compatibility with older Git, so we do it in two // rounds, "git init" then "git checkout". //if err := runGitCommandIn(dir, "init", "-b", debianBranch); err != nil { // return dir, err //} if err := runGitCommandIn(dir, "init"); err != nil { return dir, fmt.Errorf("git init: %w", err) } if err := runGitCommandIn(dir, "checkout", "-q", "-b", debianBranch); err != nil { return dir, fmt.Errorf("git checkout: %w", err) } // Set repository options if debianName := getDebianName(); debianName != "TODO" { if err := runGitCommandIn(dir, "config", "user.name", debianName); err != nil { return dir, fmt.Errorf("git config user.name: %w", err) } } if debianEmail := getDebianEmail(); debianEmail != "TODO" { if err := runGitCommandIn(dir, "config", "user.email", debianEmail); err != nil { return dir, fmt.Errorf("git config user.email: %w", err) } } if err := runGitCommandIn(dir, "config", "push.default", "matching"); err != nil { return dir, fmt.Errorf("git config push.default: %w", err) } // [remote "origin"] originURL := "git@salsa.debian.org:go-team/packages/" + debsrc + ".git" log.Printf("Adding remote \"origin\" with URL %q\n", originURL) if err := runGitCommandIn(dir, "remote", "add", "origin", originURL); err != nil { return dir, fmt.Errorf("git remote add origin %s: %w", originURL, err) } if err := runGitCommandIn(dir, "config", "--add", "remote.origin.push", "+refs/heads/*:refs/heads/*"); err != nil { return dir, fmt.Errorf("git config --add remote.origin.push */heads/*: %w", err) } if err := runGitCommandIn(dir, "config", "--add", "remote.origin.push", "+refs/tags/*:refs/tags/*"); err != nil { return dir, fmt.Errorf("git config --add remote.origin.push */tags/*: %w", err) } // Preconfigure branches branches := []string{debianBranch, "upstream"} if pristineTar { branches = append(branches, "pristine-tar") } for _, branch := range branches { if err := runGitCommandIn(dir, "config", "branch."+branch+".remote", "origin"); err != nil { return dir, fmt.Errorf("git config branch.%s.remote origin: %w", branch, err) } if err := runGitCommandIn(dir, "config", "branch."+branch+".merge", "refs/heads/"+branch); err != nil { return dir, fmt.Errorf("git config branch.%s.merge refs/heads/%s: %w", branch, branch, err) } } if includeUpstreamHistory { u.remote, err = shortHostName(gopkg, allowUnknownHoster) if err != nil { return dir, fmt.Errorf("unable to fetch upstream history: %q", err) } if u.remote == "debian" { u.remote = "salsa" } log.Printf("Adding remote %q with URL %q\n", u.remote, u.rr.Repo) if err := runGitCommandIn(dir, "remote", "add", u.remote, u.rr.Repo); err != nil { return dir, fmt.Errorf("git remote add %s %s: %w", u.remote, u.rr.Repo, err) } log.Printf("Running \"git fetch %s\"\n", u.remote) if err := runGitCommandIn(dir, "fetch", u.remote); err != nil { return dir, fmt.Errorf("git fetch %s: %w", u.remote, err) } } // Import upstream orig tarball arg := []string{"import-orig", "--no-interactive", "--debian-branch=" + debianBranch} if pristineTar { arg = append(arg, "--pristine-tar") } if includeUpstreamHistory { arg = append(arg, "--upstream-vcs-tag="+u.commitIsh) } arg = append(arg, filepath.Join(wd, orig)) cmd := exec.Command("gbp", arg...) cmd.Dir = dir cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return dir, fmt.Errorf("import-orig: %w", err) } { f, err := os.OpenFile(filepath.Join(dir, ".gitignore"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return dir, fmt.Errorf("open .gitignore: %w", err) } // Beginning newline in case the file already exists and lacks a newline // (not all editors enforce a newline at the end of the file): if _, err := f.Write([]byte("\n/.pc/\n/_build/\n")); err != nil { return dir, fmt.Errorf("write to .gitignore: %w", err) } if err := f.Close(); err != nil { return dir, fmt.Errorf("close .gitignore: %w", err) } } if err := runGitCommandIn(dir, "add", ".gitignore"); err != nil { return dir, fmt.Errorf("git add .gitignore: %w", err) } if err := runGitCommandIn(dir, "commit", "-m", "Ignore _build and quilt .pc dirs via .gitignore"); err != nil { return dir, fmt.Errorf("git commit (.gitignore): %w", err) } return dir, nil } // normalize package name into Debian standard[1] // https://www.debian.org/doc/debian-policy/ch-controlfields.html#source // Package names (both source and binary, see Package, Section 5.6.7) must // consist only of lower case letters (a-z), digits (0-9), plus (+) and minus // (-) signs, and periods (.). They must be at least two characters long and // must start with an alphanumeric character. func normalizeDebianPackageName(str string) string { lowerDigitPlusMinusDot := func(r rune) rune { switch { case r >= 'a' && r <= 'z' || '0' <= r && r <= '9': return r case r >= 'A' && r <= 'Z': return r + ('a' - 'A') case r == '.' || r == '+' || r == '-': return r case r == '_': return '-' } return -1 } safe := strings.Trim(strings.Map(lowerDigitPlusMinusDot, str), "-") if len(safe) < 2 { return "TODO" } return safe } func shortHostName(gopkg string, allowUnknownHoster bool) (host string, err error) { knownHosts := map[string]string{ // keep the list in alphabetical order "bazil.org": "bazil", "bitbucket.org": "bitbucket", "blitiri.com.ar": "blitiri", "cloud.google.com": "googlecloud", "code.google.com": "googlecode", "filippo.io": "filippo", "fyne.io": "fyne", "git.sr.ht": "sourcehut", "github.com": "github", "gitlab.com": "gitlab", "go.cypherpunks.ru": "cypherpunks", "go.mongodb.org": "mongodb", "go.opentelemetry.io": "opentelemetry", "go.step.sm": "step", "go.uber.org": "uber", "go4.org": "go4", "gocloud.dev": "gocloud", "golang.org": "golang", "google.golang.org": "google", "gopkg.in": "gopkg", "honnef.co": "honnef", "howett.net": "howett", "k8s.io": "k8s", "modernc.org": "modernc", "pault.ag": "pault", "rsc.io": "rsc", "salsa.debian.org": "debian", "sigs.k8s.io": "k8s-sigs", "software.sslmate.com": "sslmate", } parts := strings.Split(gopkg, "/") fqdn := parts[0] if host, ok := knownHosts[fqdn]; ok { return host, nil } if !allowUnknownHoster { return "", fmt.Errorf("unknown hoster %q", fqdn) } suffix, _ := publicsuffix.PublicSuffix(fqdn) host = fqdn[:len(fqdn)-len(suffix)-len(".")] log.Printf("WARNING: Using %q as canonical hostname for %q. If that is not okay, please file a bug against %s.\n", host, fqdn, os.Args[0]) return host, nil } // debianNameFromGopkg maps a Go package repo path to a Debian package name, // e.g. "golang.org/x/text" → "golang-golang-x-text". // This follows https://fedoraproject.org/wiki/PackagingDrafts/Go#Package_Names func debianNameFromGopkg(gopkg string, t packageType, customProgPkgName string, allowUnknownHoster bool) string { parts := strings.Split(gopkg, "/") if t == typeProgram || t == typeProgramLibrary { if customProgPkgName != "" { return normalizeDebianPackageName(customProgPkgName) } return normalizeDebianPackageName(parts[len(parts)-1]) } host, err := shortHostName(gopkg, allowUnknownHoster) if err != nil { log.Fatalf("Cannot derive Debian package name: %v. See -help output for -allow_unknown_hoster\n", err) } parts[0] = host return normalizeDebianPackageName("golang-" + strings.Join(parts, "-")) } func getDebianName() string { if name := strings.TrimSpace(os.Getenv("DEBFULLNAME")); name != "" { return name } if name := strings.TrimSpace(os.Getenv("DEBNAME")); name != "" { return name } if u, err := user.Current(); err == nil && u.Name != "" { return u.Name } return "TODO" } func getDebianEmail() string { if email := strings.TrimSpace(os.Getenv("DEBEMAIL")); email != "" { return email } mailname, err := os.ReadFile("/etc/mailname") // By default, /etc/mailname contains "debian" which is not useful; check for ".". if err == nil && strings.Contains(string(mailname), ".") { if u, err := user.Current(); err == nil && u.Username != "" { return u.Username + "@" + strings.TrimSpace(string(mailname)) } } return "TODO" } func writeITP(gopkg, debsrc, debversion string) (string, error) { itpname := fmt.Sprintf("itp-%s.txt", debsrc) f, err := os.Create(itpname) if err != nil { return itpname, fmt.Errorf("create file: %w", err) } defer f.Close() // TODO: memoize license, _, err := getLicenseForGopkg(gopkg) if err != nil { log.Printf("Could not determine license for %q: %v\n", gopkg, err) license = "TODO" } author, _, err := getAuthorAndCopyrightForGopkg(gopkg) if err != nil { log.Printf("Could not determine author for %q: %v\n", gopkg, err) author = "TODO" } description, err := getDescriptionForGopkg(gopkg) if err != nil { log.Printf("Could not determine description for %q: %v\n", gopkg, err) description = "TODO" } fmt.Fprintf(f, "From: %q <%s>\n", getDebianName(), getDebianEmail()) fmt.Fprintf(f, "To: submit@bugs.debian.org\n") fmt.Fprintf(f, "Subject: ITP: %s -- %s\n", debsrc, description) fmt.Fprintf(f, "Content-Type: text/plain; charset=utf-8\n") fmt.Fprintf(f, "Content-Transfer-Encoding: 8bit\n") fmt.Fprintf(f, "X-Debbugs-CC: debian-devel@lists.debian.org, debian-go@lists.debian.org\n") fmt.Fprintf(f, "\n") fmt.Fprintf(f, "Package: wnpp\n") fmt.Fprintf(f, "Severity: wishlist\n") fmt.Fprintf(f, "Owner: %s <%s>\n", getDebianName(), getDebianEmail()) fmt.Fprintf(f, "\n") fmt.Fprintf(f, "* Package name : %s\n", debsrc) fmt.Fprintf(f, " Version : %s\n", debversion) fmt.Fprintf(f, " Upstream Author : %s\n", author) fmt.Fprintf(f, "* URL : %s\n", getHomepageForGopkg(gopkg)) fmt.Fprintf(f, "* License : %s\n", license) fmt.Fprintf(f, " Programming Lang: Go\n") fmt.Fprintf(f, " Description : %s\n", description) fmt.Fprintf(f, "\n") longdescription, err := getLongDescriptionForGopkg(gopkg) if err != nil { log.Printf("Could not determine long description for %q: %v\n", gopkg, err) longdescription = "TODO: long description" } fmt.Fprintln(f, longdescription) fmt.Fprintf(f, "\n") fmt.Fprintf(f, "TODO: perhaps reasoning\n") return itpname, nil } func copyFile(src, dest string) error { input, err := os.Open(src) if err != nil { return fmt.Errorf("open: %w", err) } defer input.Close() output, err := os.Create(dest) if err != nil { return fmt.Errorf("create: %w", err) } if _, err := io.Copy(output, input); err != nil { return fmt.Errorf("copy: %w", err) } return output.Close() } func execMake(args []string, usage func()) { fs := flag.NewFlagSet("make", flag.ExitOnError) if usage != nil { fs.Usage = usage } else { fs.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s [make] [FLAG]... \n", os.Args[0]) fmt.Fprintf(os.Stderr, "Example: %s make golang.org/x/oauth2\n", os.Args[0]) fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "\"%s make\" downloads the specified Go package from the Internet,\nand creates new files and directories in the current working directory.\n", os.Args[0]) fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "Flags:\n") fs.PrintDefaults() } } var gitRevision string fs.StringVar(&gitRevision, "git_revision", "", "git revision (see gitrevisions(7)) of the specified Go package\n"+ "to check out, defaulting to the default behavior of git clone.\n"+ "Useful in case you do not want to package e.g. current HEAD.") var allowUnknownHoster bool fs.BoolVar(&allowUnknownHoster, "allow_unknown_hoster", false, "The pkg-go naming conventions use a canonical identifier for\n"+ "the hostname (see https://go-team.pages.debian.net/packaging.html),\n"+ "and the mapping is hardcoded into dh-make-golang.\n"+ "In case you want to package a Go package living on an unknown hoster,\n"+ "you may set this flag to true and double-check that the resulting\n"+ "package name is sane. Contact pkg-go if unsure.") var dep14 bool fs.BoolVar(&dep14, "dep14", true, "Follow DEP-14 branch naming and use debian/sid (instead of master)\n"+ "as the default debian-branch.") var pristineTar bool fs.BoolVar(&pristineTar, "pristine-tar", false, "Keep using a pristine-tar branch as in the old workflow.\n"+ "Discouraged, see \"pristine-tar considered harmful\"\n"+ "https://michael.stapelberg.ch/posts/2018-01-28-pristine-tar/\n"+ "and the \"Drop pristine-tar branches\" section at\n"+ "https://go-team.pages.debian.net/workflow-changes.html") var forcePrerelease bool fs.BoolVar(&forcePrerelease, "force_prerelease", false, "Package @master or @tip instead of the latest tagged version") var pkgTypeString string fs.StringVar(&pkgTypeString, "type", "", "Set package type, one of:\n"+ ` * "library" (aliases: "lib", "l", "dev")`+"\n"+ ` * "program" (aliases: "prog", "p")`+"\n"+ ` * "library+program" (aliases: "lib+prog", "l+p", "both")`+"\n"+ ` * "program+library" (aliases: "prog+lib", "p+l", "combined")`) var customProgPkgName string fs.StringVar(&customProgPkgName, "program_package_name", "", "Override the program package name, and the source package name too\n"+ "when appropriate, e.g. to name github.com/cli/cli as \"gh\"") var includeUpstreamHistory bool fs.BoolVar(&includeUpstreamHistory, "upstream_git_history", true, "Include upstream git history (Debian pkg-go team new workflow).\n"+ "New in dh-make-golang 0.3.0, currently experimental.") fs.StringVar(&wrapAndSort, "wrap-and-sort", "a", "Set how the various multi-line fields in debian/control are formatted.\n"+ "Valid values are \"a\", \"at\" and \"ast\", see wrap-and-sort(1) man page\n"+ "for more information.") // ==================================================================== // // Start actual make routine // // ==================================================================== log.Printf("Starting %q", buildVersionString()) err := fs.Parse(args) if err != nil { log.Fatalf("parse args: %v", err) } if fs.NArg() < 1 { fs.Usage() os.Exit(1) } gitRevision = strings.TrimSpace(gitRevision) gopkg := fs.Arg(0) // Ensure the specified argument is a Go package import path. rr, err := vcs.RepoRootForImportPath(gopkg, false) if err != nil { log.Fatalf("Verifying arguments: %v — did you specify a Go package import path?", err) } if gopkg != rr.Root { log.Printf("Continuing with repository root %q instead of specified import path %q (repositories are the unit of packaging in Debian)", rr.Root, gopkg) gopkg = rr.Root } // Set default source and binary package names. // Note that debsrc may change depending on the actual package type. debsrc := debianNameFromGopkg(gopkg, typeLibrary, customProgPkgName, allowUnknownHoster) debLib := debsrc + "-dev" debProg := debianNameFromGopkg(gopkg, typeProgram, customProgPkgName, allowUnknownHoster) var pkgType packageType switch strings.TrimSpace(pkgTypeString) { case "", "guess": pkgType = typeGuess case "library", "lib", "l", "dev": pkgType = typeLibrary case "program", "prog", "p": pkgType = typeProgram case "library+program", "lib+prog", "l+p", "both": // Example packages: golang-github-alecthomas-chroma, // golang-github-tdewolff-minify, golang-github-spf13-viper pkgType = typeLibraryProgram case "program+library", "prog+lib", "p+l", "combined": // Example package: hugo pkgType = typeProgramLibrary default: log.Fatalf("-type=%q not recognized, aborting\n", pkgTypeString) } // Set the debian branch. debBranch := "master" if dep14 { debBranch = "debian/sid" } switch strings.TrimSpace(wrapAndSort) { case "a": // Current default, also what "cme fix dpkg" generates wrapAndSort = "a" case "at", "ta": // -t, --trailing-comma, preferred by Martina Ferrari // and currently used in quite a few packages wrapAndSort = "at" case "ast", "ats", "sat", "sta", "tas", "tsa": // -s, --short-indent too, proposed by Guillem Jover wrapAndSort = "ast" default: log.Fatalf("%q is not a valid value for -wrap-and-sort, aborting.", wrapAndSort) } if pkgType != typeGuess { debsrc = debianNameFromGopkg(gopkg, pkgType, customProgPkgName, allowUnknownHoster) if _, err := os.Stat(debsrc); err == nil { log.Fatalf("Output directory %q already exists, aborting\n", debsrc) } } // if pkgType == typeGuess, debsrc (also the output directory) will be // determined later, i.e. after the upstream source has been downloaded. if strings.ToLower(gopkg) != gopkg { // Without -git_revision, specifying the package name in the wrong case // will lead to two checkouts, i.e. wasting bandwidth. With // -git_revision, packaging might fail. // // In case it turns out that Go package names should never contain any // uppercase letters, we can just auto-convert the argument. log.Printf("WARNING: Go package names are case-sensitive. Did you really mean %q instead of %q?\n", gopkg, strings.ToLower(gopkg)) } var ( eg errgroup.Group golangBinaries map[string]string // map[goImportPath]debianBinaryPackage ) // TODO: also check whether there already is a git repository on salsa. eg.Go(func() error { var err error golangBinaries, err = getGolangBinaries() return err }) u, err := makeUpstreamSourceTarball(gopkg, gitRevision, forcePrerelease) if err != nil { log.Fatalf("Could not create a tarball of the upstream source: %v\n", err) } if pkgType == typeGuess { if u.firstMain != "" { log.Printf("Assuming you are packaging a program (because %q defines a main package), use -type to override\n", u.firstMain) pkgType = typeProgram debsrc = debianNameFromGopkg(gopkg, pkgType, customProgPkgName, allowUnknownHoster) } else { pkgType = typeLibrary } } if _, err := os.Stat(debsrc); err == nil { log.Fatalf("Output directory %q already exists, aborting\n", debsrc) } if err := eg.Wait(); err != nil { log.Printf("Could not check for existing Go packages in Debian: %v", err) } if debbin, ok := golangBinaries[gopkg]; ok { log.Printf("WARNING: A package called %q is already in Debian! See https://tracker.debian.org/pkg/%s\n", debbin, debbin) } orig := fmt.Sprintf("%s_%s.orig.tar.%s", debsrc, u.version, u.compression) log.Printf("Moving tempfile to %q\n", orig) // We need to copy the file, merely renaming is not enough since the file // might be on a different filesystem (/tmp often is a tmpfs). if err := copyFile(u.tarPath, orig); err != nil { log.Fatalf("Could not rename orig tarball from %q to %q: %v\n", u.tarPath, orig, err) } if err := os.Remove(u.tarPath); err != nil { log.Printf("Could not remove tempfile %q: %v\n", u.tarPath, err) } debversion := u.version + "-1" dir, err := createGitRepository(debsrc, gopkg, orig, u, includeUpstreamHistory, allowUnknownHoster, debBranch, pristineTar) if err != nil { log.Fatalf("Could not create git repository: %v\n", err) } debdependencies := make([]string, 0, len(u.repoDeps)) for _, dep := range u.repoDeps { if len(golangBinaries) == 0 { // fall back to heuristic debdependencies = append(debdependencies, debianNameFromGopkg(dep, typeLibrary, "", allowUnknownHoster)+"-dev") continue } bin, ok := golangBinaries[dep] if !ok { log.Printf("Build-Dependency %q is not yet available in Debian, or has not yet been converted to use XS-Go-Import-Path in debian/control", dep) continue } debdependencies = append(debdependencies, bin) } if err := writeTemplates(dir, gopkg, debsrc, debLib, debProg, debversion, pkgType, debdependencies, u, dep14, pristineTar); err != nil { log.Fatalf("Could not create debian/ from templates: %v\n", err) } itpname, err := writeITP(gopkg, debsrc, debversion) if err != nil { log.Fatalf("Could not write ITP email: %v\n", err) } log.Println("Done!") fmt.Printf("\n") fmt.Printf("Packaging successfully created in %s\n", dir) fmt.Printf(" Source: %s\n", debsrc) switch pkgType { case typeLibrary: fmt.Printf(" Binary: %s\n", debLib) case typeProgram: fmt.Printf(" Binary: %s\n", debProg) case typeLibraryProgram: fmt.Printf(" Binary: %s\n", debLib) fmt.Printf(" Binary: %s\n", debProg) case typeProgramLibrary: fmt.Printf(" Binary: %s\n", debProg) fmt.Printf(" Binary: %s\n", debLib) } fmt.Printf("\n") fmt.Printf("Resolve all TODOs in %s, then email it out:\n", itpname) fmt.Printf(" /usr/sbin/sendmail -t < %s\n", itpname) fmt.Printf("\n") fmt.Printf("Resolve all the TODOs in debian/, find them using:\n") fmt.Printf(" grep -r TODO debian\n") fmt.Printf("\n") fmt.Printf("To build the package, commit the packaging and use gbp buildpackage:\n") fmt.Printf(" git add debian && git commit -S -m 'Initial packaging'\n") fmt.Printf(" gbp buildpackage --git-pbuilder\n") fmt.Printf("\n") fmt.Printf("To create the packaging git repository on salsa, use:\n") fmt.Printf(" dh-make-golang create-salsa-project %s\n", debsrc) fmt.Printf("\n") fmt.Printf("Once you are happy with your packaging, push it to salsa using:\n") fmt.Printf(" git push origin %s\n", debBranch) fmt.Printf(" gbp push\n") fmt.Printf("\n") if includeUpstreamHistory { fmt.Printf("NOTE: Full upstream git history has been included as per pkg-go team's\n") fmt.Printf(" new workflow. This feature is new and somewhat experimental,\n") fmt.Printf(" and all feedback are welcome!\n") fmt.Printf(" (For old behavior, use --upstream_git_history=false)\n") fmt.Printf("\n") fmt.Printf("The upstream git history is being tracked with the remote named %q.\n", u.remote) fmt.Printf("To upgrade to the latest upstream version, you may use something like:\n") fmt.Printf(" git fetch %-15v # note the latest tag or commit-ish\n", u.remote) fmt.Printf(" uscan --report-status # check we get the same tag or commit-ish\n") fmt.Printf(" gbp import-orig --sign-tags --uscan --upstream-vcs-tag=\n") fmt.Printf("\n") } } dh-make-golang-0.7.0/make_test.go000066400000000000000000000053231457726057500166210ustar00rootroot00000000000000package main import ( "testing" "golang.org/x/tools/go/vcs" ) var shortName = []struct { in string out string }{ {"", "TODO"}, {"d", "TODO"}, {"d--", "TODO"}, } func TestAcceptInput(t *testing.T) { for _, tt := range shortName { in := normalizeDebianPackageName(tt.in) if in != tt.out { t.Errorf("userInput(%q) => %q, want %q", tt.in, in, tt.out) } } } var miscName = []struct { in string out string }{ {"dh-make-golang", "dh-make-golang"}, {"DH-make-golang", "dh-make-golang"}, {"dh_make_golang", "dh-make-golang"}, {"dh_make*go&3*@@", "dh-makego3"}, {"7h_make*go&3*@@", "7h-makego3"}, {"7h_make*go&3*.@", "7h-makego3."}, {"7h_make*go+3*.@", "7h-makego+3."}, } func TestNormalizeDebianPackageName(t *testing.T) { for _, tt := range miscName { s := normalizeDebianPackageName(tt.in) if s != tt.out { t.Errorf("normalizeDebianPackageName(%q) => %q, want %q", tt.in, s, tt.out) } } } var nameFromGoPkg = []struct { in string t packageType custom string out string }{ {"github.com/Debian/dh-make-golang", typeProgram, "", "dh-make-golang"}, {"github.com/Debian/DH-make-golang", typeGuess, "", "golang-github-debian-dh-make-golang"}, {"github.com/Debian/dh_make_golang", typeGuess, "", "golang-github-debian-dh-make-golang"}, {"github.com/sean-/seed", typeGuess, "", "golang-github-sean--seed"}, {"git.sr.ht/~sircmpwn/getopt", typeGuess, "", "golang-sourcehut-sircmpwn-getopt"}, {"golang.org/x/term", typeLibrary, "", "golang-golang-x-term"}, {"github.com/cli/cli", typeProgram, "gh", "gh"}, } func TestDebianNameFromGopkg(t *testing.T) { for _, tt := range nameFromGoPkg { s := debianNameFromGopkg(tt.in, tt.t, tt.custom, false) if s != tt.out { t.Errorf("debianNameFromGopkg(%q) => %q, want %q", tt.in, s, tt.out) } } } var tarballUrl = []struct { repoRoot string tag string compression string url string }{ {"https://github.com/Debian/dh-make-golang", "0.6.0", "gz", "https://github.com/Debian/dh-make-golang/archive/0.6.0.tar.gz"}, {"https://github.com/Debian/dh-make-golang.git", "0.6.0", "gz", "https://github.com/Debian/dh-make-golang/archive/0.6.0.tar.gz"}, {"https://gitlab.com/gitlab-org/labkit", "1.3.0", "gz", "https://gitlab.com/gitlab-org/labkit/-/archive/1.3.0/labkit-1.3.0.tar.gz"}, {"https://git.sr.ht/~sircmpwn/getopt", "v1.0.0", "gz", "https://git.sr.ht/~sircmpwn/getopt/archive/v1.0.0.tar.gz"}, } func TestUpstreamTarmballUrl(t *testing.T) { for _, tt := range tarballUrl { u := upstream{ rr: &vcs.RepoRoot{Repo: tt.repoRoot}, compression: tt.compression, tag: tt.tag, } url, _ := u.tarballUrl() if url != tt.url { t.Errorf("TestUpstreamTarmballUrl(%q) => %q, want %q", tt.repoRoot, url, tt.url) } } } dh-make-golang-0.7.0/metadata.go000066400000000000000000000152701457726057500164270ustar00rootroot00000000000000package main import ( "context" "fmt" "net/http" "regexp" "strings" "golang.org/x/net/html" ) // To update, use: // curl -s https://api.github.com/licenses | jq '.[].key' // then compare with https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#license-specification var githubLicenseToDebianLicense = map[string]string{ //"agpl-3.0" (not in debian?) "apache-2.0": "Apache-2.0", "artistic-2.0": "Artistic-2.0", "bsd-2-clause": "BSD-2-clause", "bsd-3-clause": "BSD-3-clause", "cc0-1.0": "CC0-1.0", //"epl-1.0" (eclipse public license) "gpl-2.0": "GPL-2.0", // TODO: is this GPL-2.0+? "gpl-3.0": "GPL-3.0", "isc": "ISC", "lgpl-2.1": "LGPL-2.1", "lgpl-3.0": "LGPL-3.0", "mit": "Expat", "mpl-2.0": "MPL-2.0", // include in base-files >= 9.9 //"unlicense" (not in debian) } var debianLicenseText = map[string]string{ "Apache-2.0": ` Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . https://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Comment: On Debian systems, the complete text of the Apache version 2.0 license can be found in "/usr/share/common-licenses/Apache-2.0".`, "Expat": ` Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.`, "MPL-2.0": ` This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. Comment: On Debian systems, the complete text of the MPL-2.0 license can be found in "/usr/share/common-licenses/MPL-2.0".`, } var githubRegexp = regexp.MustCompile(`github\.com/([^/]+/[^/]+)`) func findGitHubOwnerRepo(gopkg string) (string, error) { if strings.HasPrefix(gopkg, "github.com/") { return strings.TrimPrefix(gopkg, "github.com/"), nil } resp, err := http.Get("https://" + gopkg + "?go-get=1") if err != nil { return "", fmt.Errorf("HTTP get: %w", err) } defer resp.Body.Close() z := html.NewTokenizer(resp.Body) for { tt := z.Next() if tt == html.ErrorToken { return "", fmt.Errorf("%q is not on GitHub", gopkg) } token := z.Token() if token.Data != "meta" { continue } var meta struct { name, content string } for _, attr := range token.Attr { if attr.Key == "name" { meta.name = attr.Val } if attr.Key == "content" { meta.content = attr.Val } } match := func(name string, length int) string { if f := strings.Fields(meta.content); meta.name == name && len(f) == length { if f[0] != gopkg { return "" } if repoMatch := githubRegexp.FindStringSubmatch(f[2]); repoMatch != nil { return repoMatch[1] } } return "" } if repo := match("go-import", 3); repo != "" { return repo, nil } if repo := match("go-source", 4); repo != "" { return repo, nil } } } func findGitHubRepo(gopkg string) (owner string, repo string, _ error) { ownerrepo, err := findGitHubOwnerRepo(gopkg) if err != nil { return "", "", fmt.Errorf("find GitHub owner repo: %w", err) } parts := strings.Split(ownerrepo, "/") if got, want := len(parts), 2; got != want { return "", "", fmt.Errorf("invalid GitHub repo: %q does not follow owner/repo", repo) } return parts[0], parts[1], nil } func getLicenseForGopkg(gopkg string) (string, string, error) { owner, repo, err := findGitHubRepo(gopkg) if err != nil { return "", "", fmt.Errorf("find GitHub repo: %w", err) } rl, _, err := gitHub.Repositories.License(context.TODO(), owner, repo) if err != nil { return "", "", fmt.Errorf("get license for Go package: %w", err) } if deblicense, ok := githubLicenseToDebianLicense[rl.GetLicense().GetKey()]; ok { fulltext := debianLicenseText[deblicense] if fulltext == "" { fulltext = " TODO" } return deblicense, fulltext, nil } return "TODO", " TODO", nil } func getAuthorAndCopyrightForGopkg(gopkg string) (string, string, error) { owner, repo, err := findGitHubRepo(gopkg) if err != nil { return "", "", fmt.Errorf("find GitHub repo: %w", err) } rr, _, err := gitHub.Repositories.Get(context.TODO(), owner, repo) if err != nil { return "", "", fmt.Errorf("get repo: %w", err) } if strings.TrimSpace(rr.GetOwner().GetURL()) == "" { return "", "", fmt.Errorf("repository owner URL not present in API response") } ur, _, err := gitHub.Users.Get(context.TODO(), rr.GetOwner().GetLogin()) if err != nil { return "", "", fmt.Errorf("get user: %w", err) } copyright := rr.CreatedAt.Format("2006") + " " + ur.GetName() if strings.HasPrefix(repo, "google/") { // As per https://opensource.google.com/docs/creating/, Google retains // the copyright for repositories underneath github.com/google/. copyright = rr.CreatedAt.Format("2006") + " Google Inc." } return ur.GetName(), copyright, nil } // getDescriptionForGopkg gets the package description from GitHub, // intended for the synopsis or the short description in debian/control. func getDescriptionForGopkg(gopkg string) (string, error) { owner, repo, err := findGitHubRepo(gopkg) if err != nil { return "", fmt.Errorf("find GitHub repo: %w", err) } rr, _, err := gitHub.Repositories.Get(context.TODO(), owner, repo) if err != nil { return "", err } return strings.TrimSpace(rr.GetDescription()), nil } func getHomepageForGopkg(gopkg string) string { owner, repo, err := findGitHubRepo(gopkg) if err != nil { return "TODO" } return "https://github.com/" + owner + "/" + repo } dh-make-golang-0.7.0/progress.go000066400000000000000000000022601457726057500165060ustar00rootroot00000000000000package main import ( "fmt" "github.com/mattn/go-isatty" "os" "path/filepath" "strings" "time" ) const ( _ = 1 << (10 * iota) Kibi Mebi Gibi Tebi ) func humanizeBytes(b int64) string { if b > Tebi { return fmt.Sprintf("%.2f TiB", float64(b)/float64(Tebi)) } else if b > Gibi { return fmt.Sprintf("%.2f GiB", float64(b)/float64(Gibi)) } else if b > Mebi { return fmt.Sprintf("%.2f MiB", float64(b)/float64(Mebi)) } else { return fmt.Sprintf("%.2f KiB", float64(b)/float64(Kibi)) } } func progressSize(prefix, path string, done chan struct{}) { // previous holds how many bytes the previous line contained // so that we can clear it in its entirety. var previous int tty := isatty.IsTerminal(os.Stdout.Fd()) for { if tty { var usage int64 filepath.Walk(path, func(path string, info os.FileInfo, err error) error { if err == nil && info.Mode().IsRegular() { usage += info.Size() } return nil }) fmt.Printf("\r%s", strings.Repeat(" ", previous)) previous, _ = fmt.Printf("\r%s: %s", prefix, humanizeBytes(usage)) } select { case <-done: fmt.Printf("\r") return case <-time.After(250 * time.Millisecond): break } } } dh-make-golang-0.7.0/search.go000066400000000000000000000036671457726057500161230ustar00rootroot00000000000000package main import ( "encoding/json" "flag" "fmt" "log" "net/http" "os" "regexp" "strings" ) const ( golangBinariesURL = "https://api.ftp-master.debian.org/binary/by_metadata/Go-Import-Path" ) func getGolangBinaries() (map[string]string, error) { golangBinaries := make(map[string]string) resp, err := http.Get(golangBinariesURL) if err != nil { return nil, fmt.Errorf("getting %q: %w", golangBinariesURL, err) } if got, want := resp.StatusCode, http.StatusOK; got != want { return nil, fmt.Errorf("unexpected HTTP status code: got %d, want %d", got, want) } var pkgs []struct { Binary string `json:"binary"` XSGoImportPath string `json:"metadata_value"` Source string `json:"source"` } if err := json.NewDecoder(resp.Body).Decode(&pkgs); err != nil { return nil, fmt.Errorf("decode: %w", err) } for _, pkg := range pkgs { if !strings.HasSuffix(pkg.Binary, "-dev") { continue // skip -dbgsym packages etc. } for _, importPath := range strings.Split(pkg.XSGoImportPath, ",") { // XS-Go-Import-Path can be comma-separated and contain spaces. golangBinaries[strings.TrimSpace(importPath)] = pkg.Binary } } return golangBinaries, nil } func execSearch(args []string) { fs := flag.NewFlagSet("search", flag.ExitOnError) fs.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s search \n", os.Args[0]) fmt.Fprintf(os.Stderr, "Uses Go's default regexp syntax (https://golang.org/pkg/regexp/syntax/)\n") fmt.Fprintf(os.Stderr, "Example: %s search 'debi.*'\n", os.Args[0]) } err := fs.Parse(args) if err != nil { log.Fatal(err) } if fs.NArg() != 1 { fs.Usage() os.Exit(1) } pattern, err := regexp.Compile(fs.Arg(0)) if err != nil { log.Fatal(err) } golangBinaries, err := getGolangBinaries() if err != nil { log.Fatal(err) } for importPath, binary := range golangBinaries { if pattern.MatchString(importPath) { fmt.Printf("%s: %s\n", binary, importPath) } } } dh-make-golang-0.7.0/template.go000066400000000000000000000352071457726057500164640ustar00rootroot00000000000000package main import ( "fmt" "log" "os" "path/filepath" "sort" "strings" "time" ) func writeTemplates(dir, gopkg, debsrc, debLib, debProg, debversion string, pkgType packageType, dependencies []string, u *upstream, dep14, pristineTar bool, ) error { if err := os.Mkdir(filepath.Join(dir, "debian"), 0755); err != nil { // If upstream debian dir exists, try to move it aside, and then below. if err := os.Rename(filepath.Join(dir, "debian"), filepath.Join(dir, "upstream_debian")); err != nil { return fmt.Errorf("rename debian/ to upstream_debian/: %w", err) } else { // Second attempt to create template debian dir, after moving upstream dir aside. if err := os.Mkdir(filepath.Join(dir, "debian"), 0755); err != nil { return fmt.Errorf("mkdir debian/: %w", err) } if err := os.Rename(filepath.Join(dir, "upstream_debian"), filepath.Join(dir, "debian/upstream_debian")); err != nil { return fmt.Errorf("move upstream_debian into debian/: %w", err) } log.Printf("WARNING: Upstream debian/ dir found, and relocated to debian/upstream_debian/\n") } } if err := os.Mkdir(filepath.Join(dir, "debian", "source"), 0755); err != nil { return fmt.Errorf("mkdir debian/source/: %w", err) } if err := writeDebianGitIgnore(dir, debLib, debProg, pkgType); err != nil { return fmt.Errorf("write debian/.gitignore: %w", err) } if err := writeDebianChangelog(dir, debsrc, debversion); err != nil { return fmt.Errorf("write changelog: %w", err) } if err := writeDebianControl(dir, gopkg, debsrc, debLib, debProg, pkgType, dependencies); err != nil { return fmt.Errorf("write control: %w", err) } if err := writeDebianCopyright(dir, gopkg, u.vendorDirs, u.hasGodeps); err != nil { return fmt.Errorf("write copyright: %w", err) } if err := writeDebianRules(dir, pkgType); err != nil { return fmt.Errorf("write rules: %w", err) } var repack bool = len(u.vendorDirs) > 0 || u.hasGodeps if err := writeDebianWatch(dir, gopkg, debsrc, u.hasRelease, repack); err != nil { return fmt.Errorf("write watch: %w", err) } if err := writeDebianSourceFormat(dir); err != nil { return fmt.Errorf("write source/format: %w", err) } if err := writeDebianPackageInstall(dir, debLib, debProg, pkgType); err != nil { return fmt.Errorf("write install: %w", err) } if err := writeDebianUpstreamMetadata(dir, gopkg); err != nil { return fmt.Errorf("write upstream metadata: %w", err) } if err := writeDebianGbpConf(dir, dep14, pristineTar); err != nil { return fmt.Errorf("write gbp conf: %w", err) } if err := writeDebianGitLabCI(dir); err != nil { return fmt.Errorf("write GitLab CI: %w", err) } return nil } func writeDebianGitIgnore(dir, debLib, debProg string, pkgType packageType) error { f, err := os.Create(filepath.Join(dir, "debian", ".gitignore")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "*.debhelper\n") fmt.Fprintf(f, "*.log\n") fmt.Fprintf(f, "*.substvars\n") fmt.Fprintf(f, "/.debhelper/\n") fmt.Fprintf(f, "/debhelper-build-stamp\n") fmt.Fprintf(f, "/files\n") switch pkgType { case typeLibrary: fmt.Fprintf(f, "/%s/\n", debLib) case typeProgram: fmt.Fprintf(f, "/%s/\n", debProg) case typeLibraryProgram: fallthrough case typeProgramLibrary: fmt.Fprintf(f, "/%s/\n", debLib) fmt.Fprintf(f, "/%s/\n", debProg) default: log.Fatalf("Invalid pkgType %d in writeDebianGitIgnore(), aborting", pkgType) } return nil } func writeDebianChangelog(dir, debsrc, debversion string) error { f, err := os.Create(filepath.Join(dir, "debian", "changelog")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "%s (%s) UNRELEASED; urgency=medium\n", debsrc, debversion) fmt.Fprintf(f, "\n") fmt.Fprintf(f, " * Initial release (Closes: TODO)\n") fmt.Fprintf(f, "\n") fmt.Fprintf(f, " -- %s <%s> %s\n", getDebianName(), getDebianEmail(), time.Now().Format("Mon, 02 Jan 2006 15:04:05 -0700")) return nil } func fprintfControlField(f *os.File, field string, valueArray []string) { switch wrapAndSort { case "a": // Current default, also what "cme fix dpkg" generates fmt.Fprintf(f, "%s: %s\n", field, strings.Join(valueArray, ",\n"+strings.Repeat(" ", len(field)+2))) case "at": // -t, --trailing-comma, preferred by Martina Ferrari // and currently used in quite a few packages fmt.Fprintf(f, "%s: %s,\n", field, strings.Join(valueArray, ",\n"+strings.Repeat(" ", len(field)+2))) case "ast": // -s, --short-indent too, proposed by Guillem Jover fmt.Fprintf(f, "%s:\n %s,\n", field, strings.Join(valueArray, ",\n ")) default: log.Fatalf("%q is not a valid value for -wrap-and-sort, aborting.", wrapAndSort) } } func addDescription(f *os.File, gopkg, comment string) { description, err := getDescriptionForGopkg(gopkg) if err != nil { log.Printf("Could not determine description for %q: %v\n", gopkg, err) description = "TODO: short description" } fmt.Fprintf(f, "Description: %s %s\n", description, comment) longdescription, err := getLongDescriptionForGopkg(gopkg) if err != nil { log.Printf("Could not determine long description for %q: %v\n", gopkg, err) longdescription = "TODO: long description" } fmt.Fprintln(f, longdescription) } func addLibraryPackage(f *os.File, gopkg, debLib string, dependencies []string) { fmt.Fprintf(f, "\n") fmt.Fprintf(f, "Package: %s\n", debLib) fmt.Fprintf(f, "Architecture: all\n") fmt.Fprintf(f, "Multi-Arch: foreign\n") deps := dependencies sort.Strings(deps) deps = append(deps, "${misc:Depends}") fprintfControlField(f, "Depends", deps) addDescription(f, gopkg, "(library)") } func addProgramPackage(f *os.File, gopkg, debProg string) { fmt.Fprintf(f, "\n") fmt.Fprintf(f, "Package: %s\n", debProg) fmt.Fprintf(f, "Section: TODO\n") fmt.Fprintf(f, "Architecture: any\n") deps := []string{"${misc:Depends}", "${shlibs:Depends}"} fprintfControlField(f, "Depends", deps) fmt.Fprintf(f, "Built-Using: ${misc:Built-Using}\n") addDescription(f, gopkg, "(program)") } func writeDebianControl(dir, gopkg, debsrc, debLib, debProg string, pkgType packageType, dependencies []string) error { f, err := os.Create(filepath.Join(dir, "debian", "control")) if err != nil { return err } defer f.Close() // Source package: fmt.Fprintf(f, "Source: %s\n", debsrc) fmt.Fprintf(f, "Section: golang\n") fmt.Fprintf(f, "Priority: optional\n") fmt.Fprintf(f, "Maintainer: Debian Go Packaging Team \n") fprintfControlField(f, "Uploaders", []string{getDebianName() + " <" + getDebianEmail() + ">"}) fmt.Fprintf(f, "Rules-Requires-Root: no\n") builddeps := append([]string{ "debhelper-compat (= 13)", "dh-sequence-golang", "golang-any"}, dependencies...) sort.Strings(builddeps) fprintfControlField(f, "Build-Depends", builddeps) fmt.Fprintf(f, "Testsuite: autopkgtest-pkg-go\n") fmt.Fprintf(f, "Standards-Version: 4.6.2\n") fmt.Fprintf(f, "Vcs-Browser: https://salsa.debian.org/go-team/packages/%s\n", debsrc) fmt.Fprintf(f, "Vcs-Git: https://salsa.debian.org/go-team/packages/%s.git\n", debsrc) fmt.Fprintf(f, "Homepage: %s\n", getHomepageForGopkg(gopkg)) fmt.Fprintf(f, "XS-Go-Import-Path: %s\n", gopkg) // Binary package(s): switch pkgType { case typeLibrary: addLibraryPackage(f, gopkg, debLib, dependencies) case typeProgram: addProgramPackage(f, gopkg, debProg) case typeLibraryProgram: addLibraryPackage(f, gopkg, debLib, dependencies) addProgramPackage(f, gopkg, debProg) case typeProgramLibrary: addProgramPackage(f, gopkg, debProg) addLibraryPackage(f, gopkg, debLib, dependencies) default: log.Fatalf("Invalid pkgType %d in writeDebianControl(), aborting", pkgType) } return nil } func writeDebianCopyright(dir, gopkg string, vendorDirs []string, hasGodeps bool) error { license, fulltext, err := getLicenseForGopkg(gopkg) if err != nil { log.Printf("Could not determine license for %q: %v\n", gopkg, err) license = "TODO" fulltext = "TODO" } f, err := os.Create(filepath.Join(dir, "debian", "copyright")) if err != nil { return err } defer f.Close() _, copyright, err := getAuthorAndCopyrightForGopkg(gopkg) if err != nil { log.Printf("Could not determine copyright for %q: %v\n", gopkg, err) copyright = "TODO" } var indent = " " var linebreak = "" if wrapAndSort == "ast" { indent = " " linebreak = "\n" } fmt.Fprintf(f, "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n") fmt.Fprintf(f, "Source: %s\n", getHomepageForGopkg(gopkg)) fmt.Fprintf(f, "Upstream-Name: %s\n", filepath.Base(gopkg)) fmt.Fprintf(f, "Upstream-Contact: TODO\n") if len(vendorDirs) > 0 || hasGodeps { fmt.Fprintf(f, "Files-Excluded:\n") for _, dir := range vendorDirs { fmt.Fprintf(f, indent+"%s\n", dir) } if hasGodeps { fmt.Fprintf(f, indent+"Godeps/_workspace\n") } } fmt.Fprintf(f, "\n") fmt.Fprintf(f, "Files:"+linebreak+" *\n") fmt.Fprintf(f, "Copyright:"+linebreak+" %s\n", copyright) fmt.Fprintf(f, "License: %s\n", license) fmt.Fprintf(f, "\n") fmt.Fprintf(f, "Files:"+linebreak+" debian/*\n") fmt.Fprintf(f, "Copyright:"+linebreak+" %s %s <%s>\n", time.Now().Format("2006"), getDebianName(), getDebianEmail()) fmt.Fprintf(f, "License: %s\n", license) fmt.Fprintf(f, "Comment: Debian packaging is licensed under the same terms as upstream\n") fmt.Fprintf(f, "\n") fmt.Fprintf(f, "License: %s\n", license) fmt.Fprint(f, fulltext) fmt.Fprint(f, "\n") return nil } func writeDebianRules(dir string, pkgType packageType) error { f, err := os.Create(filepath.Join(dir, "debian", "rules")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "#!/usr/bin/make -f\n") fmt.Fprintf(f, "\n") fmt.Fprintf(f, "%%:\n") fmt.Fprintf(f, "\tdh $@ --builddirectory=_build --buildsystem=golang\n") if pkgType == typeProgram { fmt.Fprintf(f, "\n") fmt.Fprintf(f, "override_dh_auto_install:\n") fmt.Fprintf(f, "\tdh_auto_install -- --no-source\n") } if err := os.Chmod(filepath.Join(dir, "debian", "rules"), 0755); err != nil { return err } return nil } func writeDebianSourceFormat(dir string) error { f, err := os.Create(filepath.Join(dir, "debian", "source", "format")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "3.0 (quilt)\n") return nil } func writeDebianGbpConf(dir string, dep14, pristineTar bool) error { if !(dep14 || pristineTar) { return nil } f, err := os.Create(filepath.Join(dir, "debian", "gbp.conf")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "[DEFAULT]\n") if dep14 { fmt.Fprintf(f, "debian-branch = debian/sid\n") fmt.Fprintf(f, "dist = DEP14\n") } if pristineTar { fmt.Fprintf(f, "pristine-tar = True\n") } return nil } func writeDebianWatch(dir, gopkg, debsrc string, hasRelease bool, repack bool) error { // TODO: Support other hosters too host := "github.com" owner, repo, err := findGitHubRepo(gopkg) if err != nil { log.Printf("debian/watch: Unable to resolve %s to github.com, skipping\n", gopkg) return nil } if !strings.HasPrefix(gopkg, "github.com/") { log.Printf("debian/watch: %s resolves to %s/%s/%s\n", gopkg, host, owner, repo) } f, err := os.Create(filepath.Join(dir, "debian", "watch")) if err != nil { return err } defer f.Close() filenamemanglePattern := `s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%@PACKAGE@-$1.tar.gz%` uversionmanglePattern := `s/(\d)[_\.\-\+]?(RC|rc|pre|dev|beta|alpha)[.]?(\d*)$/$1~$2$3/` if hasRelease { log.Printf("Setting debian/watch to track release tarball") fmt.Fprint(f, "version=4\n") fmt.Fprint(f, `opts="filenamemangle=`+filenamemanglePattern+`,\`+"\n") fmt.Fprint(f, ` uversionmangle=`+uversionmanglePattern) if repack { fmt.Fprint(f, `,\`+"\n") fmt.Fprint(f, ` dversionmangle=s/\+ds\d*$//,repacksuffix=+ds1`) } fmt.Fprint(f, `" \`+"\n") fmt.Fprintf(f, ` https://%s/%s/%s/tags .*/v?(\d\S*)\.tar\.gz debian`+"\n", host, owner, repo) } else { log.Printf("Setting debian/watch to track git HEAD") fmt.Fprint(f, "version=4\n") fmt.Fprint(f, `opts="mode=git, pgpmode=none`) if repack { fmt.Fprint(f, `,\`+"\n") fmt.Fprint(f, ` dversionmangle=s/\+ds\d*$//,repacksuffix=+ds1`) } fmt.Fprint(f, `" \`+"\n") fmt.Fprintf(f, ` https://%s/%s/%s.git \`+"\n", host, owner, repo) fmt.Fprint(f, " HEAD debian\n") // Anticipate that upstream would eventually switch to tagged releases fmt.Fprint(f, "\n") fmt.Fprint(f, "# Use the following when upstream starts to tag releases:\n") fmt.Fprint(f, "#\n") fmt.Fprint(f, "#version=4\n") fmt.Fprint(f, `#opts="filenamemangle=`+filenamemanglePattern+`,\`+"\n") fmt.Fprint(f, `# uversionmangle=`+uversionmanglePattern) if repack { fmt.Fprint(f, `,\`+"\n") fmt.Fprint(f, `# dversionmangle=s/\+ds\d*$//,repacksuffix=+ds1`) } fmt.Fprint(f, `" \`+"\n") fmt.Fprintf(f, `# https://%s/%s/%s/tags .*/v?(\d\S*)\.tar\.gz debian`+"\n", host, owner, repo) } return nil } func writeDebianPackageInstall(dir, debLib, debProg string, pkgType packageType) error { if pkgType == typeLibraryProgram || pkgType == typeProgramLibrary { f, err := os.Create(filepath.Join(dir, "debian", debProg+".install")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "usr/bin\n") f, err = os.Create(filepath.Join(dir, "debian", debLib+".install")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "usr/share\n") } return nil } func writeDebianUpstreamMetadata(dir, gopkg string) error { // TODO: Support other hosters too host := "github.com" owner, repo, err := findGitHubRepo(gopkg) if err != nil { log.Printf("debian/upstream/metadata: Unable to resolve %s to github.com, skipping\n", gopkg) return nil } if !strings.HasPrefix(gopkg, "github.com/") { log.Printf("debian/upstream/metadata: %s resolves to %s/%s/%s\n", gopkg, host, owner, repo) } if err := os.Mkdir(filepath.Join(dir, "debian", "upstream"), 0755); err != nil { return err } f, err := os.Create(filepath.Join(dir, "debian", "upstream", "metadata")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "---\n") fmt.Fprintf(f, "Bug-Database: https://%s/%s/%s/issues\n", host, owner, repo) fmt.Fprintf(f, "Bug-Submit: https://%s/%s/%s/issues/new\n", host, owner, repo) fmt.Fprintf(f, "Repository: https://%s/%s/%s.git\n", host, owner, repo) fmt.Fprintf(f, "Repository-Browse: https://%s/%s/%s\n", host, owner, repo) return nil } func writeDebianGitLabCI(dir string) error { const gitlabciymlTmpl = `# auto-generated, DO NOT MODIFY. # The authoritative copy of this file lives at: # https://salsa.debian.org/go-team/infra/pkg-go-tools/blob/master/config/gitlabciyml.go --- include: - https://salsa.debian.org/go-team/infra/pkg-go-tools/-/raw/master/pipeline/test-archive.yml ` f, err := os.Create(filepath.Join(dir, "debian", "gitlab-ci.yml")) if err != nil { return err } defer f.Close() fmt.Fprint(f, gitlabciymlTmpl) return nil } dh-make-golang-0.7.0/version.go000066400000000000000000000114011457726057500163240ustar00rootroot00000000000000package main import ( "fmt" "log" "os" "os/exec" "regexp" "strconv" "strings" "time" "unicode" ) var ( // describeRegexp parses the count and revision part of the “git describe --long” output. describeRegexp = regexp.MustCompile(`-\d+-g([0-9a-f]+)\s*$`) // semverRegexp checks if a string is a valid Go semver, // from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string // with leading "v" added. semverRegexp = regexp.MustCompile(`^v(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) // uversionPrereleaseRegexp checks for upstream pre-release // so that '-' can be replaced with '~' in pkgVersionFromGit. // To be kept in sync with the regexp portion of uversionmanglePattern in template.go uversionPrereleaseRegexp = regexp.MustCompile(`(\d)[_\.\-\+]?(RC|rc|pre|dev|beta|alpha)[.]?(\d*)$`) ) // pkgVersionFromGit determines the actual version to be packaged // from the git repository status and user preference. // Besides returning the Debian upstream version, the "upstream" struct // struct fields u.version, u.commitIsh, u.hasRelease and u.isRelease // are also set. // TODO: also support other VCS func pkgVersionFromGit(gitdir string, u *upstream, forcePrerelease bool) (string, error) { var latestTag string var commitsAhead int // Find @latest version tag (whether annotated or not) cmd := exec.Command("git", "describe", "--abbrev=0", "--tags", "--exclude", "*/v*") cmd.Dir = gitdir if out, err := cmd.Output(); err == nil { latestTag = strings.TrimSpace(string(out)) u.hasRelease = true u.tag = latestTag log.Printf("Found latest tag %q", latestTag) if !semverRegexp.MatchString(latestTag) { log.Printf("WARNING: Latest tag %q is not a valid SemVer version\n", latestTag) // TODO: Enforce strict sementic versioning with leading "v"? } // Count number of commits since @latest version cmd = exec.Command("git", "rev-list", "--count", latestTag+"..HEAD") cmd.Dir = gitdir out, err := cmd.Output() if err != nil { return "", fmt.Errorf("git rev-list: %w", err) } commitsAhead, err = strconv.Atoi(strings.TrimSpace(string(out))) if err != nil { return "", fmt.Errorf("parse commits ahead: %w", err) } if commitsAhead == 0 { // Equivalent to "git describe --exact-match --tags" log.Printf("Latest tag %q matches master", latestTag) } else { log.Printf("INFO: master is ahead of %q by %v commits", latestTag, commitsAhead) } u.commitIsh = latestTag // Mangle latestTag into Debian upstream_version // TODO: Move to function and write unit test? u.version = strings.TrimLeftFunc( uversionPrereleaseRegexp.ReplaceAllString(latestTag, "$1~$2$3"), func(r rune) bool { return !unicode.IsNumber(r) }, ) if forcePrerelease { log.Printf("INFO: Force packaging master (prerelease) as requested by user") // Fallthrough to package @master (prerelease) } else { u.isRelease = true return u.version, nil } } // Packaging @master (prerelease) // 1.0~rc1 < 1.0 < 1.0+b1, as per // https://www.debian.org/doc/manuals/maint-guide/first.en.html#namever mainVer := "0.0~" if u.hasRelease { mainVer = u.version + "+" } // Find committer date, UNIX timestamp cmd = exec.Command("git", "log", "--pretty=format:%ct", "-n1", "--no-show-signature") cmd.Dir = gitdir lastCommitUnixBytes, err := cmd.Output() if err != nil { return "", fmt.Errorf("git log: %w", err) } lastCommitUnix, err := strconv.ParseInt(strings.TrimSpace(string(lastCommitUnixBytes)), 0, 64) if err != nil { return "", fmt.Errorf("parse last commit date: %w", err) } // This results in an output like "v4.10.2-232-g9f107c8" cmd = exec.Command("git", "describe", "--long", "--tags") cmd.Dir = gitdir lastCommitHash := "" describeBytes, err := cmd.Output() if err != nil { // In case there are no tags at all, we just use the sha of the current commit cmd = exec.Command("git", "rev-parse", "--short", "HEAD") cmd.Dir = gitdir cmd.Stderr = os.Stderr revparseBytes, err := cmd.Output() if err != nil { return "", fmt.Errorf("git rev-parse: %w", err) } lastCommitHash = strings.TrimSpace(string(revparseBytes)) u.commitIsh = lastCommitHash } else { submatches := describeRegexp.FindSubmatch(describeBytes) if submatches == nil { return "", fmt.Errorf("git describe output %q does not match expected format", string(describeBytes)) } lastCommitHash = string(submatches[1]) u.commitIsh = strings.TrimSpace(string(describeBytes)) } u.version = fmt.Sprintf("%sgit%s.%s", mainVer, time.Unix(lastCommitUnix, 0).UTC().Format("20060102"), lastCommitHash) return u.version, nil } dh-make-golang-0.7.0/version_current.go000066400000000000000000000011131457726057500200650ustar00rootroot00000000000000package main import ( "fmt" "runtime" ) // Version represents the dh-make-golang build version. type Version struct { major int minor int patch int preRelease string } var currentVersion = Version{ major: 0, minor: 7, patch: 0, preRelease: "", } func (v Version) String() string { return fmt.Sprintf("%d.%d.%d%s", v.major, v.minor, v.patch, v.preRelease) } func buildVersionString() string { version := "v" + currentVersion.String() osArch := runtime.GOOS + "/" + runtime.GOARCH return fmt.Sprintf("%s %s %s", program, version, osArch) } dh-make-golang-0.7.0/version_test.go000066400000000000000000000060221457726057500173660ustar00rootroot00000000000000package main import ( "os" "os/exec" "path/filepath" "strings" "testing" ) func gitCmdOrFatal(t *testing.T, tempdir string, arg ...string) { cmd := exec.Command("git", arg...) cmd.Dir = tempdir cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { t.Fatalf("Could not run %v: %v", cmd.Args, err) } } func TestSnapshotVersion(t *testing.T) { os.Setenv("TZ", "UTC") defer os.Unsetenv("TZ") tempdir, err := os.MkdirTemp("", "dh-make-golang") if err != nil { t.Fatalf("Could not create temp dir: %v", err) } defer os.RemoveAll(tempdir) tempfile := filepath.Join(tempdir, "test") if err := os.WriteFile(tempfile, []byte("testcase"), 0644); err != nil { t.Fatalf("Could not write temp file %q: %v", tempfile, err) } gitCmdOrFatal(t, tempdir, "init") gitCmdOrFatal(t, tempdir, "config", "user.email", "unittest@example.com") gitCmdOrFatal(t, tempdir, "config", "user.name", "Unit Test") gitCmdOrFatal(t, tempdir, "add", "test") cmd := exec.Command("git", "commit", "-a", "-m", "initial commit") cmd.Env = append(os.Environ(), "GIT_COMMITTER_DATE=2015-04-20T11:22:33") cmd.Dir = tempdir cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { t.Fatalf("Could not run %v: %v", cmd.Args, err) } var u upstream got, err := pkgVersionFromGit(tempdir, &u, false) if err != nil { t.Fatalf("Determining package version from git failed: %v", err) } if want := "0.0~git20150420."; !strings.HasPrefix(got, want) { t.Errorf("got %q, want %q", got, want) } gitCmdOrFatal(t, tempdir, "tag", "-a", "v1", "-m", "release v1") got, err = pkgVersionFromGit(tempdir, &u, false) if err != nil { t.Fatalf("Determining package version from git failed: %v", err) } if want := "1"; got != want { t.Logf("got %q, want %q", got, want) } if err := os.WriteFile(tempfile, []byte("testcase 2"), 0644); err != nil { t.Fatalf("Could not write temp file %q: %v", tempfile, err) } cmd = exec.Command("git", "commit", "-a", "-m", "first change") cmd.Env = append(os.Environ(), "GIT_COMMITTER_DATE=2015-05-07T11:22:33") cmd.Dir = tempdir cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { t.Fatalf("Could not run %v: %v", cmd.Args, err) } got, err = pkgVersionFromGit(tempdir, &u, false) if err != nil { t.Fatalf("Determining package version from git failed: %v", err) } if want := "1+git20150507.1."; !strings.HasPrefix(got, want) { t.Logf("got %q, want %q", got, want) } if err := os.WriteFile(tempfile, []byte("testcase 3"), 0644); err != nil { t.Fatalf("Could not write temp file %q: %v", tempfile, err) } cmd = exec.Command("git", "commit", "-a", "-m", "second change") cmd.Env = append(os.Environ(), "GIT_COMMITTER_DATE=2015-05-08T11:22:33") cmd.Dir = tempdir cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { t.Fatalf("Could not run %v: %v", cmd.Args, err) } got, err = pkgVersionFromGit(tempdir, &u, false) if err != nil { t.Fatalf("Determining package version from git failed: %v", err) } if want := "1+git20150508.2."; !strings.HasPrefix(got, want) { t.Logf("got %q, want %q", got, want) } }