具有基于源文件目录的动态输出目录的 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
将:
- 处理
./packages/bar/src/index.js
-> ./packages/bar/lib/index.js
- 处理
./packages/bar/src/other-file.js
-> ./packages/bar/lib/other-file.js
我也可能希望将此模式用于 ./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 目标添加为通用 all
和 clean
规则的先决条件。
像 foo
、clean-bar
、all
和 clean
这样的目标将完全按照您的预期运行。
我正在尝试使用 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
将:
- 处理
./packages/bar/src/index.js
->./packages/bar/lib/index.js
- 处理
./packages/bar/src/other-file.js
->./packages/bar/lib/other-file.js
我也可能希望将此模式用于 ./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 目标添加为通用 all
和 clean
规则的先决条件。
像 foo
、clean-bar
、all
和 clean
这样的目标将完全按照您的预期运行。