// Copyright 2019 The Hugo Authors. All rights reserved.
//
// 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
// http://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.
package modules
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/gohugoio/hugo/common/hexec"
hglob "github.com/gohugoio/hugo/hugofs/glob"
"github.com/gobwas/glob"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/rogpeppe/go-internal/module"
"github.com/gohugoio/hugo/common/hugio"
"github.com/pkg/errors"
"github.com/spf13/afero"
)
var fileSeparator = string(os.PathSeparator)
const (
goBinaryStatusOK goBinaryStatus = iota
goBinaryStatusNotFound
goBinaryStatusTooOld
)
// The "vendor" dir is reserved for Go Modules.
const vendord = "_vendor"
const (
goModFilename = "go.mod"
goSumFilename = "go.sum"
)
// NewClient creates a new Client that can be used to manage the Hugo Components
// in a given workingDir.
// The Client will resolve the dependencies recursively, but needs the top
// level imports to start out.
func NewClient(cfg ClientConfig) *Client {
fs := cfg.Fs
n := filepath.Join(cfg.WorkingDir, goModFilename)
goModEnabled, _ := afero.Exists(fs, n)
var goModFilename string
if goModEnabled {
goModFilename = n
}
env := os.Environ()
mcfg := cfg.ModuleConfig
config.SetEnvVars(&env,
"PWD", cfg.WorkingDir,
"GO111MODULE", "on",
"GOPROXY", mcfg.Proxy,
"GOPRIVATE", mcfg.Private,
"GONOPROXY", mcfg.NoProxy)
if cfg.CacheDir != "" {
// Module cache stored below $GOPATH/pkg
config.SetEnvVars(&env, "GOPATH", cfg.CacheDir)
}
logger := cfg.Logger
if logger == nil {
logger = loggers.NewWarningLogger()
}
var noVendor glob.Glob
if cfg.ModuleConfig.NoVendor != "" {
noVendor, _ = hglob.GetGlob(hglob.NormalizePath(cfg.ModuleConfig.NoVendor))
}
return &Client{
fs: fs,
ccfg: cfg,
logger: logger,
noVendor: noVendor,
moduleConfig: mcfg,
environ: env,
GoModulesFilename: goModFilename,
}
}
// Client contains most of the API provided by this package.
type Client struct {
fs afero.Fs
logger loggers.Logger
noVendor glob.Glob
ccfg ClientConfig
// The top level module config
moduleConfig Config
// Environment variables used in "go get" etc.
environ []string
// Set when Go modules are initialized in the current repo, that is:
// a go.mod file exists.
GoModulesFilename string
// Set if we get a exec.ErrNotFound when running Go, which is most likely
// due to being run on a system without Go installed. We record it here
// so we can give an instructional error at the end if module/theme
// resolution fails.
goBinaryStatus goBinaryStatus
}
// Graph writes a module dependenchy graph to the given writer.
func (c *Client) Graph(w io.Writer) error {
mc, coll := c.collect(true)
if coll.err != nil {
return coll.err
}
for _, module := range mc.AllModules {
if module.Owner() == nil {
continue
}
prefix := ""
if module.Disabled() {
prefix = "DISABLED "
}
dep := pathVersion(module.Owner()) + " " + pathVersion(module)
if replace := module.Replace(); replace != nil {
if replace.Version() != "" {
dep += " => " + pathVersion(replace)
} else