github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/common/hugo/hugo.go (about)

     1  // Copyright 2018 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package hugo
    15  
    16  import (
    17  	"fmt"
    18  	"html/template"
    19  	"os"
    20  	"path/filepath"
    21  	"runtime/debug"
    22  	"sort"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/bep/godartsass"
    28  	"github.com/gohugoio/hugo/common/hexec"
    29  	"github.com/gohugoio/hugo/hugofs/files"
    30  
    31  	"github.com/spf13/afero"
    32  
    33  	"github.com/gohugoio/hugo/config"
    34  	"github.com/gohugoio/hugo/hugofs"
    35  )
    36  
    37  const (
    38  	EnvironmentDevelopment = "development"
    39  	EnvironmentProduction  = "production"
    40  )
    41  
    42  var (
    43  	// buildDate allows vendor-specified build date when .git/ is unavailable.
    44  	buildDate string
    45  	// vendorInfo contains vendor notes about the current build.
    46  	vendorInfo string
    47  )
    48  
    49  // Info contains information about the current Hugo environment
    50  type Info struct {
    51  	CommitHash string
    52  	BuildDate  string
    53  
    54  	// The build environment.
    55  	// Defaults are "production" (hugo) and "development" (hugo server).
    56  	// This can also be set by the user.
    57  	// It can be any string, but it will be all lower case.
    58  	Environment string
    59  
    60  	// version of go that the Hugo binary was built with
    61  	GoVersion string
    62  
    63  	deps []*Dependency
    64  }
    65  
    66  // Version returns the current version as a comparable version string.
    67  func (i Info) Version() VersionString {
    68  	return CurrentVersion.Version()
    69  }
    70  
    71  // Generator a Hugo meta generator HTML tag.
    72  func (i Info) Generator() template.HTML {
    73  	return template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s">`, CurrentVersion.String()))
    74  }
    75  
    76  func (i Info) IsProduction() bool {
    77  	return i.Environment == EnvironmentProduction
    78  }
    79  
    80  func (i Info) IsExtended() bool {
    81  	return IsExtended
    82  }
    83  
    84  // Deps gets a list of dependencies for this Hugo build.
    85  func (i Info) Deps() []*Dependency {
    86  	return i.deps
    87  }
    88  
    89  // NewInfo creates a new Hugo Info object.
    90  func NewInfo(environment string, deps []*Dependency) Info {
    91  	if environment == "" {
    92  		environment = EnvironmentProduction
    93  	}
    94  	var (
    95  		commitHash string
    96  		buildDate  string
    97  		goVersion  string
    98  	)
    99  
   100  	bi := getBuildInfo()
   101  	if bi != nil {
   102  		commitHash = bi.Revision
   103  		buildDate = bi.RevisionTime
   104  		goVersion = bi.GoVersion
   105  	}
   106  
   107  	return Info{
   108  		CommitHash:  commitHash,
   109  		BuildDate:   buildDate,
   110  		Environment: environment,
   111  		deps:        deps,
   112  		GoVersion:   goVersion,
   113  	}
   114  }
   115  
   116  // GetExecEnviron creates and gets the common os/exec environment used in the
   117  // external programs we interact with via os/exec, e.g. postcss.
   118  func GetExecEnviron(workDir string, cfg config.Provider, fs afero.Fs) []string {
   119  	var env []string
   120  	nodepath := filepath.Join(workDir, "node_modules")
   121  	if np := os.Getenv("NODE_PATH"); np != "" {
   122  		nodepath = workDir + string(os.PathListSeparator) + np
   123  	}
   124  	config.SetEnvVars(&env, "NODE_PATH", nodepath)
   125  	config.SetEnvVars(&env, "PWD", workDir)
   126  	config.SetEnvVars(&env, "HUGO_ENVIRONMENT", cfg.GetString("environment"))
   127  	config.SetEnvVars(&env, "HUGO_ENV", cfg.GetString("environment"))
   128  
   129  	config.SetEnvVars(&env, "HUGO_PUBLISHDIR", filepath.Join(workDir, cfg.GetString("publishDirOrig")))
   130  
   131  	if fs != nil {
   132  		fis, err := afero.ReadDir(fs, files.FolderJSConfig)
   133  		if err == nil {
   134  			for _, fi := range fis {
   135  				key := fmt.Sprintf("HUGO_FILE_%s", strings.ReplaceAll(strings.ToUpper(fi.Name()), ".", "_"))
   136  				value := fi.(hugofs.FileMetaInfo).Meta().Filename
   137  				config.SetEnvVars(&env, key, value)
   138  			}
   139  		}
   140  	}
   141  
   142  	return env
   143  }
   144  
   145  type buildInfo struct {
   146  	VersionControlSystem string
   147  	Revision             string
   148  	RevisionTime         string
   149  	Modified             bool
   150  
   151  	GoOS   string
   152  	GoArch string
   153  
   154  	*debug.BuildInfo
   155  }
   156  
   157  var bInfo *buildInfo
   158  var bInfoInit sync.Once
   159  
   160  func getBuildInfo() *buildInfo {
   161  	bInfoInit.Do(func() {
   162  		bi, ok := debug.ReadBuildInfo()
   163  		if !ok {
   164  			return
   165  		}
   166  
   167  		bInfo = &buildInfo{BuildInfo: bi}
   168  
   169  		for _, s := range bInfo.Settings {
   170  			switch s.Key {
   171  			case "vcs":
   172  				bInfo.VersionControlSystem = s.Value
   173  			case "vcs.revision":
   174  				bInfo.Revision = s.Value
   175  			case "vcs.time":
   176  				bInfo.RevisionTime = s.Value
   177  			case "vcs.modified":
   178  				bInfo.Modified = s.Value == "true"
   179  			case "GOOS":
   180  				bInfo.GoOS = s.Value
   181  			case "GOARCH":
   182  				bInfo.GoArch = s.Value
   183  			}
   184  		}
   185  
   186  	})
   187  
   188  	return bInfo
   189  }
   190  
   191  func formatDep(path, version string) string {
   192  	return fmt.Sprintf("%s=%q", path, version)
   193  }
   194  
   195  // GetDependencyList returns a sorted dependency list on the format package="version".
   196  // It includes both Go dependencies and (a manually maintained) list of C(++) dependencies.
   197  func GetDependencyList() []string {
   198  	var deps []string
   199  
   200  	bi := getBuildInfo()
   201  	if bi == nil {
   202  		return deps
   203  	}
   204  
   205  	for _, dep := range bi.Deps {
   206  		deps = append(deps, formatDep(dep.Path, dep.Version))
   207  	}
   208  
   209  	deps = append(deps, GetDependencyListNonGo()...)
   210  
   211  	sort.Strings(deps)
   212  
   213  	return deps
   214  }
   215  
   216  // GetDependencyListNonGo returns a list of non-Go dependencies.
   217  func GetDependencyListNonGo() []string {
   218  	var deps []string
   219  
   220  	if IsExtended {
   221  		deps = append(
   222  			deps,
   223  			formatDep("github.com/sass/libsass", "3.6.5"),
   224  			formatDep("github.com/webmproject/libwebp", "v1.2.4"),
   225  		)
   226  	}
   227  
   228  	if dartSass := dartSassVersion(); dartSass.ProtocolVersion != "" {
   229  		const dartSassPath = "github.com/sass/dart-sass-embedded"
   230  		deps = append(deps,
   231  			formatDep(dartSassPath+"/protocol", dartSass.ProtocolVersion),
   232  			formatDep(dartSassPath+"/compiler", dartSass.CompilerVersion),
   233  			formatDep(dartSassPath+"/implementation", dartSass.ImplementationVersion),
   234  		)
   235  	}
   236  	return deps
   237  }
   238  
   239  // IsRunningAsTest reports whether we are running as a test.
   240  func IsRunningAsTest() bool {
   241  	for _, arg := range os.Args {
   242  		if strings.HasPrefix(arg, "-test") {
   243  			return true
   244  		}
   245  	}
   246  	return false
   247  }
   248  
   249  // Dependency is a single dependency, which can be either a Hugo Module or a local theme.
   250  type Dependency struct {
   251  	// Returns the path to this module.
   252  	// This will either be the module path, e.g. "github.com/gohugoio/myshortcodes",
   253  	// or the path below your /theme folder, e.g. "mytheme".
   254  	Path string
   255  
   256  	// The module version.
   257  	Version string
   258  
   259  	// Whether this dependency is vendored.
   260  	Vendor bool
   261  
   262  	// Time version was created.
   263  	Time time.Time
   264  
   265  	// In the dependency tree, this is the first module that defines this module
   266  	// as a dependency.
   267  	Owner *Dependency
   268  
   269  	// Replaced by this dependency.
   270  	Replace *Dependency
   271  }
   272  
   273  func dartSassVersion() godartsass.DartSassVersion {
   274  	// This is also duplicated in the dartsass package.
   275  	const dartSassEmbeddedBinaryName = "dart-sass-embedded"
   276  	if !hexec.InPath(dartSassEmbeddedBinaryName) {
   277  		return godartsass.DartSassVersion{}
   278  	}
   279  	v, _ := godartsass.Version(dartSassEmbeddedBinaryName)
   280  	return v
   281  }