具有基于源文件目录的动态输出目录的 Makefile

Makefile with Dynamic Output Directory Based on Source File Directory

我正在尝试使用 monorepo 创建 Makefile,但在基于源文件目录的动态输出目录方面遇到了一些困难。

我的项目布局如下:

% tree .
.
├── Makefile
└── packages
    ├── bar
    │   └── src
    │       └── index.js
    │       └── other-file.js
    └── foo
        └── src
            └── index.js

5 directories, 4 files

我想处理每个包内的每个 *.js 文件(例如,./packages/foo/src/index.js)并将输出发送到另一个目录(具体来说,./packages/foo/lib/index.js)。

例如 make bar 将:

我也可能希望将此模式用于 ./src 中的其他类型的文件,例如处理 .less 个文件。

生成文件:

PACKAGES_ROOT = ./packages
packages := $(shell ls $(PACKAGES_ROOT))

package_source_dir := $(addprefix packages/,$(addsuffix /src/,$(packages)))
package_dest_dir := $(subst src,lib,$(package_source_dir))

package_source := $(foreach sdir, $(package_source_dir), $(wildcard $(sdir)*.js*))
package_dest := $(subst src,lib,$(package_source))

.PHONY: all checkdirs clean
all: checkdirs

checkdirs: $(package_dest_dir)

$(package_dest_dir):
    mkdir -p $@

$(packages):
    @echo "$@"

clean:
    rm -rf $(package_dest_dir)

我不确定我是否需要使用 VPATH 或什么...帮助?

注意:Makefile 目前刚好足以创建和删除所有目标目录。

您可以生成一个临时 makefile:

packages:= $(shell ls $(PACKAGES_ROOT))

packages.mk : Makefile $(abspath .)
    for package in $(packages); do \
        echo "packages/$(package)/lib/%.js:packages/$(package)/src/%.js" >> $@ ; \
        echo "    echo recipe for $(dir)" >> $@; \
        echo >> $@; \
    done

-include "packages.mk"

这假设目录不是由其他规则动态创建的,因为 $(packages) 将仅包含首次读取 makefile 时存在的目录。

像这样的东西应该可以完成工作:

# Directories
PKGS_ROOT := packages
PKGS_SRCDIR := src
PKGS_OUTDIR := lib

# Expands to the source directory for the specified package
pkg-srcdir = $(PKGS_ROOT)//$(PKGS_SRCDIR)
# Expands to the output directory for the specified package
pkg-libdir = $(PKGS_ROOT)//$(PKGS_OUTDIR)
# Expands to all output targets for the specified package
pkg-libs = $(addprefix $(call pkg-libdir,)/,$(notdir $(wildcard $(call pkg-srcdir,)/*.js)))

# Defines the following rules for the specified package:
#  - build rule for .js files
#  - rule to create the output directory if missing
#  - package rule to build all outputs
#  - clean-package rule to remove the output directory
# Adds the following prerequisites:
#  - package target to 'all' rule
#  - clean-package target to 'clean' rule
define pkg-rules
$(call pkg-libdir,)/%.js: $(call pkg-srcdir,)/%.js | $(call pkg-libdir,)
    @echo Making $$@ from $$^
    @cp $$^ $$@
$(call pkg-libdir,):
    @mkdir $$@
: $(call pkg-libs,)
clean-:
    rm -rf $(call pkg-libdir,)
all: 
clean: clean-
.PHONY:  clean-
endef

# Creates rules for the specified package
add-pkg = $(eval $(call pkg-rules,))

# Create rules for all packages
PKGS := $(notdir $(wildcard $(PKGS_ROOT)/*))
$(foreach p,$(PKGS),$(call add-pkg,$p))

# Will be filled in by pkg-rules
.PHONY: all clean

.DEFAULT_GOAL := all

个人functions/helpers/manipulation应该不难理解。我将目录抽象为前三个助手,以避免在需要时更改大量的事件。让我们深入 pkg-rules,其中定义了实际的动态规则。

pkg-rules 通过 foo。它将创建以下规则:

  • packages/foo/lib/%.js: packages/foo/src/%.js | packages/foo/lib:这是构建输出.js文件的模式规则。它还将输出目录作为仅顺序先决条件(与执行顺序无关 - 这意味着只有目录目标不存在时才会构建,而不查看时间戳)。
  • packages/foo/lib:此规则创建输出目录。您没有嵌套目录,如果目录存在,则不会执行配方,因此不需要 -p。顺便说一句,嵌套目录和复制源代码树是可能的,但它需要更多的技巧(可能是二次扩展,$(@D),使用更聪明的 patsubst 代替 notdir ,将 shell 用于递归通配符、提取目录等)。
  • foo: packages/foo/lib/index.js:在评论中我称之为 "package target/rule"。这是一个空的虚假规则,它将所有包输出作为先决条件。
  • clean-foo:在评论中我称之为"clean-package target/rule"。这是一个删除包输出目录的虚假规则。

它还将 package 和 clean-package 目标添加为通用 allclean 规则的先决条件。

fooclean-barallclean 这样的目标将完全按照您的预期运行。