Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to use the package as library #70

Open
dcu opened this issue Jun 29, 2023 · 5 comments
Open

Allow to use the package as library #70

dcu opened this issue Jun 29, 2023 · 5 comments

Comments

@dcu
Copy link

dcu commented Jun 29, 2023

there are some cases where using as library would be preferable than using as cli

is there a reason to not allow it? thanks

@egonelbre
Copy link
Member

egonelbre commented Jul 3, 2023

Mostly, I don't want to put the effort into a stable API. I could provide an unstable one.

Is there a concrete problem you are finding difficult to solve? I'm mostly interested in which parts of the system you are interested in being exposed.

Sometimes using golang.org/x/tools/go/packages directly might be more easier.

@dcu
Copy link
Author

dcu commented Jul 3, 2023

I would use pkgset.Calc to find all dependencies of a package

@egonelbre
Copy link
Member

egonelbre commented Jul 3, 2023

If you just want all the packages, then this would be sufficient:

package main

import (
	"golang.org/x/tools/go/packages"
)

func Dependencies(pkgs ...string) (map[string]*packages.Package, error) {
	roots, err := packages.Load(&packages.Config{
		Mode: packages.NeedName | packages.NeedImports,
		// Tests: true, // if you need tests
	}, pkgs...)
	if err != nil {
		return nil, fmt.Errorf("failed to load packages %v: %w", pkgs, err)
	}

	r := map[string]*packages.Package{}
	include(r, roots)
	return r, nil
}

func include(r map[string]*packages.Package, pkgs []*packages.Package) {
	for _, pkg := range pkgs {
		if _, added := r[pkg.ID]; added {
			continue
		}
		r[pkg.ID] = pkg
		include(r, pkg.Imports)
	}
}

@HenrikPoulsen
Copy link

Is there a concrete problem you are finding difficult to solve?

For me I wanted to experiment with using it in a CI setup, to figure out which packages need to be re-built and tested based on changes in a PR (this is for a monorepo) and stuff like that.
I would want to avoid reverse engineering the whole thing, or forking it just to export it 😅
I could most likely accomplish this with parsing the goda output, but in general I think this sort of thing is nicer to do programmatically.

Mostly, I don't want to put the effort into a stable API. I could provide an unstable one.

I at least think that's reasonable. I would be fine under the assumption that when a new release is out, it likely means I need to change my code

@egonelbre
Copy link
Member

egonelbre commented Nov 6, 2024

If you can cache the build output, then Go won't test anything that hasn't changed.

Otherwise, the logic for such a things is approximately this:

// !!!
// WARNING: CODE UNTESTED
// !!!

package main

import (
	"golang.org/x/tools/go/packages"
)

// TODO: you'll need extra conditions for changing testdata, environment flags, build flags, go.mod, go.work etc.

func NeedsRetest(pkgs, modifiedPackages []string) ([]string, error) {
	roots, err := packages.Load(&packages.Config{
		Mode: packages.NeedName | packages.NeedImports,
		Tests: true,
	}, pkgs...)
	if err != nil {
		return nil, fmt.Errorf("failed to load packages %v: %w", pkgs, err)
	}

	allPackages := map[string]*packages.Package{}
	include(allPackages, roots)

	retest := map[string]bool{}
	for _, modifiedPackage := range modifiedPackages {
		retest[modifiedPackage] = true
	}

	// check whether parent has any child that needs modification,
	// and keep around a cache to avoid double checking packages.
	check := func(parent *packages.Package) bool {
		if needs, ok := retest[parent.ID]; ok {
			return needs
		}

		for _, pkg := range parent.Imports {
			if check(pkg) {
				retest[parent.ID] = true
				return true
			}
		}

		return false
	}

	for _, pkg := range allPackages {
		check(pkg)
	}

	result := []string{}
	for pkg, ok := range retest {
		if ok {
			result = append(result, pkg)
		}
	}

	sort.Strings(result)
	return result, nil
}

func include(r map[string]*packages.Package, pkgs []*packages.Package) {
	for _, pkg := range pkgs {
		if _, added := r[pkg.ID]; added {
			continue
		}
		r[pkg.ID] = pkg
		include(r, pkg.Imports)
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants