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

Add resource vendoring #13310

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion commands/commandeer.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,11 @@ func (r *rootCommand) Name() string {
}

func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
b := newHugoBuilder(r, nil)
var vendor bool
if vendorCmd, ok := cd.Command.(vendoredCommand); ok {
vendor = vendorCmd.IsVendorCommand()
}
b := newHugoBuilder(r, nil, vendor)

if !r.buildWatch {
defer b.postBuild("Total", time.Now())
Expand Down
23 changes: 19 additions & 4 deletions commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import (
func newExec() (*simplecobra.Exec, error) {
rootCmd := &rootCommand{
commands: []simplecobra.Commander{
newHugoBuildCmd(),
newHugoBuildCmd(false),
newHugoBuildCmd(true),
newVersionCmd(),
newEnvCommand(),
newServerCommand(),
Expand All @@ -42,26 +43,40 @@ func newExec() (*simplecobra.Exec, error) {
return simplecobra.New(rootCmd)
}

func newHugoBuildCmd() simplecobra.Commander {
return &hugoBuildCommand{}
func newHugoBuildCmd(vendor bool) simplecobra.Commander {
return &hugoBuildCommand{
vendor: vendor,
}
}

// hugoBuildCommand just delegates to the rootCommand.
type hugoBuildCommand struct {
rootCmd *rootCommand
vendor bool
}

func (c *hugoBuildCommand) Commands() []simplecobra.Commander {
return nil
}

func (c *hugoBuildCommand) Name() string {
if c.vendor {
return "vendor"
}
return "build"
}

type vendoredCommand interface {
IsVendorCommand() bool
}

func (c *hugoBuildCommand) IsVendorCommand() bool {
return c.vendor
}

func (c *hugoBuildCommand) Init(cd *simplecobra.Commandeer) error {
c.rootCmd = cd.Root.Command.(*rootCommand)
return c.rootCmd.initRootCommand("build", cd)
return c.rootCmd.initRootCommand(c.Name(), cd)
}

func (c *hugoBuildCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
Expand Down
4 changes: 3 additions & 1 deletion commands/hugobuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ type hugoBuilder struct {
showErrorInBrowser bool

errState hugoBuilderErrState

vendor bool
}

var errConfigNotSet = errors.New("config not set")
Expand Down Expand Up @@ -1046,11 +1048,11 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
}
}
cfg.Set("environment", c.r.environment)

cfg.Set("internal", maps.Params{
"running": running,
"watch": watch,
"verbose": c.r.isVerbose(),
"vendor": c.vendor,
"fastRenderMode": c.fastRenderMode,
})

Expand Down
4 changes: 3 additions & 1 deletion commands/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,15 @@ const (
configChangeGoWork = "go work file"
)

func newHugoBuilder(r *rootCommand, s *serverCommand, onConfigLoaded ...func(reloaded bool) error) *hugoBuilder {
func newHugoBuilder(r *rootCommand, s *serverCommand, vendor bool, onConfigLoaded ...func(reloaded bool) error) *hugoBuilder {
var visitedURLs *types.EvictingQueue[string]
if s != nil && !s.disableFastRender {
visitedURLs = types.NewEvictingQueue[string](20)
}
return &hugoBuilder{
r: r,
s: s,
vendor: vendor,
visitedURLs: visitedURLs,
fullRebuildSem: semaphore.NewWeighted(1),
debounce: debounce.New(4 * time.Second),
Expand Down Expand Up @@ -563,6 +564,7 @@ func (c *serverCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
c.hugoBuilder = newHugoBuilder(
c.r,
c,
false,
func(reloaded bool) error {
if !reloaded {
if err := c.createServerPorts(cd); err != nil {
Expand Down
19 changes: 12 additions & 7 deletions common/hugio/writers.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,25 @@ func NewMultiWriteCloser(writeClosers ...io.WriteCloser) io.WriteCloser {
return multiWriteCloser{Writer: io.MultiWriter(writers...), closers: writeClosers}
}

// NewWriteCloser creates a new io.WriteCloser with the given writer and closer.
func NewWriteCloser(w io.Writer, closer io.Closer) io.WriteCloser {
return struct {
io.Writer
io.Closer
}{
w,
closer,
}
}

// ToWriteCloser creates an io.WriteCloser from the given io.Writer.
// If it's not already, one will be created with a Close method that does nothing.
func ToWriteCloser(w io.Writer) io.WriteCloser {
if rw, ok := w.(io.WriteCloser); ok {
return rw
}

return struct {
io.Writer
io.Closer
}{
w,
io.NopCloser(nil),
}
return NewWriteCloser(w, io.NopCloser(nil))
}

// ToReadCloser creates an io.ReadCloser from the given io.Reader.
Expand Down
6 changes: 6 additions & 0 deletions common/hugo/hugo.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ func (i HugoInfo) IsExtended() bool {
return IsExtended
}

// IsVendor returns whether we're running as `hugo vendor`.
func (i HugoInfo) IsVendor() bool {
return i.conf.Vendor()
}

// WorkingDir returns the project working directory.
func (i HugoInfo) WorkingDir() string {
return i.conf.WorkingDir()
Expand Down Expand Up @@ -166,6 +171,7 @@ type ConfigProvider interface {
WorkingDir() string
IsMultihost() bool
IsMultilingual() bool
Vendor() bool
}

// NewInfo creates a new Hugo Info object.
Expand Down
9 changes: 9 additions & 0 deletions common/maps/ordered.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ func NewOrdered[K comparable, T any]() *Ordered[K, T] {
return &Ordered[K, T]{values: make(map[K]T)}
}

// Contains returns whether the map contains the given key.
func (m *Ordered[K, T]) Contains(key K) bool {
if m == nil {
return false
}
_, found := m.values[key]
return found
}

// Set sets the value for the given key.
// Note that insertion order is not affected if a key is re-inserted into the map.
func (m *Ordered[K, T]) Set(key K, value T) {
Expand Down
1 change: 1 addition & 0 deletions config/allconfig/allconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type InternalConfig struct {
Watch bool
FastRenderMode bool
LiveReloadPort int
Vendor bool
}

// All non-params config keys for language.
Expand Down
4 changes: 4 additions & 0 deletions config/allconfig/configlanguage.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ func (c ConfigLanguage) Watching() bool {
return c.m.Base.Internal.Watch
}

func (c ConfigLanguage) Vendor() bool {
return c.m.Base.Internal.Vendor
}

func (c ConfigLanguage) NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager {
if !c.Watching() {
return identity.NopManager
Expand Down
1 change: 1 addition & 0 deletions config/commonConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func (b BuildConfig) clone() BuildConfig {
return b
}

// TODO1 remove, but first add a deprecation warning somewhere.
func (b BuildConfig) UseResourceCache(err error) bool {
if b.UseResourceCacheWhen == "never" {
return false
Expand Down
1 change: 1 addition & 0 deletions config/configProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type AllProvider interface {
BuildDrafts() bool
Running() bool
Watching() bool
Vendor() bool
NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager
FastRenderMode() bool
PrintUnusedTemplates() bool
Expand Down
38 changes: 38 additions & 0 deletions hugofs/dirsmerger.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,41 @@ var AppendDirsMerger overlayfs.DirsMerger = func(lofi, bofi []fs.DirEntry) []fs.

return lofi
}

// DirsMergerPreserveDuplicateFunc returns a DirsMerger that will preserve any duplicate
// as defined by the given func.
func DirsMergerPreserveDuplicateFunc(preserveDuplicate func(fs.DirEntry) bool) overlayfs.DirsMerger {
return func(lofi, bofi []fs.DirEntry) []fs.DirEntry {
for _, fi1 := range bofi {
var found bool
if !preserveDuplicate(fi1) {
for _, fi2 := range lofi {
if fi1.Name() == fi2.Name() {
found = true
break
}
}
}
if !found {
lofi = append(lofi, fi1)
}
}
return lofi
}
}

var FuncDirsMerger2 overlayfs.DirsMerger = func(lofi, bofi []fs.DirEntry) []fs.DirEntry {
for _, bofi := range bofi {
var found bool
for _, lofi := range lofi {
if bofi.Name() == lofi.Name() {
found = true
break
}
}
if !found {
lofi = append(lofi, bofi)
}
}
return lofi
}
3 changes: 2 additions & 1 deletion hugofs/files/classifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ const (
ComponentFolderAssets = "assets"
ComponentFolderI18n = "i18n"

FolderResources = "resources"
FolderVendor = "_vendor"
FolderResources = "resources" // TODO1 remove.
FolderJSConfig = "_jsconfig" // Mounted below /assets with postcss.config.js etc.

NameContentData = "_content"
Expand Down
4 changes: 4 additions & 0 deletions hugofs/rootmapping_fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
for _, rm := range rms {
(&rm).clean()

if rm.From == files.FolderVendor {
continue
}

rm.FromBase = files.ResolveComponentFolder(rm.From)

if len(rm.To) < 2 {
Expand Down
29 changes: 22 additions & 7 deletions hugolib/filesystems/basefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package filesystems
import (
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -241,7 +242,11 @@ type SourceFilesystems struct {

// Writable filesystem on top the project's resources directory,
// with any sub module's resource fs layered below.
ResourcesCache afero.Fs
ResourcesCache afero.Fs // TODO1 remove this.

// A writable filesystem on top of the project's vendor directory
// with any sub module's vendor fs layered.
VendorFs afero.Fs

// The work folder (may be a composite of project and theme components).
Work afero.Fs
Expand Down Expand Up @@ -569,6 +574,7 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
b.result.Layouts = createView(files.ComponentFolderLayouts, b.theBigFs.overlayMounts)
b.result.Assets = createView(files.ComponentFolderAssets, b.theBigFs.overlayMounts)
b.result.ResourcesCache = b.theBigFs.overlayResources
b.result.VendorFs = b.theBigFs.overlayVendor
b.result.RootFss = b.theBigFs.rootFss

// data and i18n needs a different merge strategy.
Expand Down Expand Up @@ -628,6 +634,9 @@ func (b *sourceFilesystemsBuilder) createMainOverlayFs(p *paths.Paths) (*filesys
overlayMountsStatic: overlayfs.New(overlayfs.Options{DirsMerger: hugofs.LanguageDirsMerger}),
overlayFull: overlayfs.New(overlayfs.Options{}),
overlayResources: overlayfs.New(overlayfs.Options{FirstWritable: true}),
overlayVendor: overlayfs.New(overlayfs.Options{FirstWritable: true, DirsMerger: hugofs.DirsMergerPreserveDuplicateFunc(func(fi fs.DirEntry) bool {
return fi.Name() == "resources.json"
})}),
}

mods := p.AllModules()
Expand Down Expand Up @@ -678,6 +687,7 @@ func (b *sourceFilesystemsBuilder) createOverlayFs(
collector.overlayMountsFull = appendNopIfEmpty(collector.overlayMountsFull)
collector.overlayFull = appendNopIfEmpty(collector.overlayFull)
collector.overlayResources = appendNopIfEmpty(collector.overlayResources)
collector.overlayVendor = appendNopIfEmpty(collector.overlayVendor)

return nil
}
Expand All @@ -696,7 +706,16 @@ func (b *sourceFilesystemsBuilder) createOverlayFs(
return md.dir, hpaths.AbsPathify(md.dir, path)
}

modBase := collector.sourceProject
if !md.isMainProject {
modBase = collector.sourceModules
}

for i, mount := range md.Mounts() {
if mount.Target == files.FolderVendor {
collector.overlayVendor = collector.overlayVendor.Append(hugofs.NewBasePathFs(modBase, mount.Source))
continue
}
// Add more weight to early mounts.
// When two mounts contain the same filename,
// the first entry wins.
Expand Down Expand Up @@ -744,11 +763,6 @@ func (b *sourceFilesystemsBuilder) createOverlayFs(
}
}

modBase := collector.sourceProject
if !md.isMainProject {
modBase = collector.sourceModules
}

sourceStatic := modBase

rmfs, err := hugofs.NewRootMappingFs(modBase, fromTo...)
Expand Down Expand Up @@ -831,7 +845,8 @@ type filesystemsCollector struct {
overlayMountsStatic *overlayfs.OverlayFs
overlayMountsFull *overlayfs.OverlayFs
overlayFull *overlayfs.OverlayFs
overlayResources *overlayfs.OverlayFs
overlayResources *overlayfs.OverlayFs // TODO1 remove
overlayVendor *overlayfs.OverlayFs

rootFss []*hugofs.RootMappingFs

Expand Down
19 changes: 19 additions & 0 deletions hugolib/hugo_sites_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
if err := h.postProcess(infol); err != nil {
h.SendError(fmt.Errorf("postProcess: %w", err))
}

if err := h.writeVendor(infol); err != nil {
h.SendError(fmt.Errorf("writeVendor: %w", err))
}
}

if h.Metrics != nil {
Expand Down Expand Up @@ -693,6 +697,21 @@ func (h *HugoSites) postProcess(l logg.LevelLogger) error {
return g.Wait()
}

func (h *HugoSites) writeVendor(l logg.LevelLogger) error {
if !h.Conf.Vendor() {
return nil
}
l = l.WithField("step", "writeVendor")
defer loggers.TimeTrackf(l, time.Now(), nil, "")

v := h.ResourceSpec.Vendorer
if err := v.Finalize(); err != nil {
return err
}

return nil
}

func (h *HugoSites) writeBuildStats() error {
if h.ResourceSpec == nil {
panic("h.ResourceSpec is nil")
Expand Down
Loading
Loading