Custom targets using patterns in Makefile - makefile

I have a tree of files that looks like this:
.
├── Makefile
├── README.md
├── exercises
│   ├── 100-exercises.ipynb
│   ├── 200-exercises.ipynb
│   ├── 300-exercises.ipynb
│   └── 400-exercises.ipynb
├── notes
│   ├── 101-notes-pandas.ipynb
│   ├── 102-notes-matplotlib-1.ipynb
│   ├── 103-notes-numpy-scipy.ipynb
│   └── 104-notes-matplotlib-seaborn.ipynb
└── tasks
├── 101-tasks-pandas.ipynb
├── 102-tasks-matplotlib-1.ipynb
├── 103-tasks-numpy-scipy.ipynb
└── 104-tasks-matplotlib-seaborn.ipynb
I would like to add some targets that only operates on files according to patterns in their filename. For example:
make lecture-1
make lecture-1-notes
make lecture-1-exercises
make lecture-2
make notes
make exercises
...
etc.
where lecture-1 refers to the set of targets that have a filename beginning with a 1 e.g. tasks/101-tasks-pandas.ipynb - to be clear the patterns are:
notes -> ./notes/*
exercises -> ./exercises/*
tasks -> ./tasks/*
lecture-1 -> ./*/1[0-9][0-9]*.ipynb
lecture-2 -> ./*/2[0-9][0-9]*.ipynb
lecture-1-notes -> ./notes/1[0-9][0-9]*.ipynb
The long way would be to have a separate target for each but I feel like there must be some kind of pattern/regex matching that can be done to avoid this.
EDIT:
For more information on the operations done on each target, I have an executable command which basically converts the IPython notebook to HTML. This is stored as a variable in make called RENDER_HTML
e.g. At the moment to render everything in the notes folder I have the following sections in my Makefile:
RENDER_HTML=jupyter nbconvert --execute --to html
NOTES_TARGETS=$(wildcard ./notes/*.ipynb)
...
.PHONY: notes
notes: ${NOTES_TARGETS}
#mkdir -p $#/html/
${RENDER_HTML} $^
#mv $#/*.html $#/html/

It would be a lot easier for us to help you if you provided example of what kinds of rules you want and what they would do: maybe implementing rules for one of these by hand that can serve as an example.
Without having any idea what the targets and prerequisites are, what I suggest is that you use recursive make to compute a list of targets to build; something like this:
lecture-%:
$(MAKE) $(patsubst ???,???,$(wildcard */$(*)*))
I just used ??? here since you don't provide any information on how the source files are translated to targets: you'll have to do that part yourself :).
If you can define a rule that builds a single output file, like this:
notes/%:
#mkdir -p $(#D)/html/
${RENDER_HTML} $#.ipynb
#mv $(#D)/*.html $(#D)/html/
then you can do this:
lecture-%:
$(MAKE) $(patsubst %.ipynb,%,$(wildcard */$(*)*))
The mv command there confuses me somewhat (seems like there should be a better way to do that for sure) but it's what you have in your question so I guess it's right.
I'm suspicious that this won't work for you, depending on the answer to my question above. If the render command needs to see ALL the files (for example to build an index.html or something) then I don't quite understand how you want this to work, when it builds only some of the files. Basically, the problem is still under-specified to allow us to give a working solution. But maybe there's enough info here to get you started.

Related

Filename does not contain on macbook zsh

On macbook I'm trying to compile all proto files in a folder that do not contain the word server. Zsh and bash seem to be working a bit different than on linux. My coworker could get it wo work by using
[^_server]
in the path on linux. But that does not seem to work on mac. I managed to get this
protoc --dart_out=grpc:proto/ ./api/(core|editor|google)/**/*(_server).proto --proto_path=./api
which compiles all the proto files containing _server in the filename in the folders core, editor and google. But I need it to be all files that don't contain _server in the name in the folders core, editor and google.
Try this:
setopt extendedglob
protoc --dart_out=grpc:proto/ \
./api/(core|editor|google)/**/(^*_server).proto \
--proto_path=./api
The demonstrations of the glob patterns below use this directory tree:
api/
├── core/
│   ├── core_notserver.proto
│   ├── core_server.proto
│   ├── core_server.txt
│   └── coresub/
│   ├── coresub_notserver.proto
│   └── coresub_server.proto
├── editor/
│   ├── ed_notserver.proto
│   └── ed_server.proto
├── google/
│   ├── ends with excluded letter e.proto
│   ├── ends with included letter a.proto
│   ├── gl_server.proto
│   └── gl_server.txt
└── other/
├── other_notserver.proto
└── other_server.proto
With _server in the filename:
> print -l ./api/(core|editor|google)/**/*_server.proto
./api/core/core_server.proto
./api/core/coresub/coresub_server.proto
./api/editor/ed_server.proto
./api/google/gl_server.proto
Without _server in the filename.
There are two ways to do a pattern negation with zsh - they both require enabling extended glob patterns, with setopt extendedglob. The option just needs to be set once, e.g. in ~/.zshrc:
> setopt extendedglob
> print -l ./api/(core|editor|google)/**/(*~*_server).proto
./api/core/core_notserver.proto
./api/core/coresub/coresub_notserver.proto
./api/editor/ed_notserver.proto
./api/google/ends with excluded letter e.proto
./api/google/ends with included letter a.proto
> print -l ./api/(core|editor|google)/**/(^*_server).proto
./api/core/core_notserver.proto
./api/core/coresub/coresub_notserver.proto
./api/editor/ed_notserver.proto
./api/google/ends with excluded letter e.proto
./api/google/ends with included letter a.proto
The pattern [^_server] will not give the results you're looking for. With square braces, the values are used as individual characters to include, or with ^ exclude, from the results. It is not treated as the sequence _server. Your coworker may have had a set of filenames that gave the appearance of working properly.
Here, any filename with a base part that ends with _, s, e, r, or v will be excluded from the glob results:
> print -l ./api/(core|editor|google)/**/*[^_server].proto
./api/google/ends with included letter a.proto
More info on globbing here.

How to run a command for each subdirectory with a Makefile?

I have the following structure:
├── configure.ac
├── dir1
│   └── Makefile.am
├── dir2
│   └── Makefile.am
├── Makefile.am
...
configure.ac:
AC_INIT([amhello], [1.0], [bug-report#address])
AM_INIT_AUTOMAKE([foreign -Wall -Werror])
AC_PROG_CC
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([
Makefile
dir1/Makefile
dir2/Makefile
])
# Libtool
AM_PROG_AR
LT_INIT
AM_EXTRA_RECURSIVE_TARGETS([foo])
AC_OUTPUT
The top-level Makefile.am
SUBDIRS = dir1 dir2
foo-start:
#echo "executing foo!"
make foo
Both dir1 and dir2 Makefile.am's look like this:
foo-local:
echo "I'm here! $$(basename $(CURDIR))"
Running this yields the output like this:
execute foo!
I'm here! dir1
I'm here! dir2
Is there a way to write the subdirectory's Makefiles so that I don't have to write the foo-local task twice?
Is there a way to write the subdirectory's Makefiles so that I don't have to write the foo-local task twice?
You can put the rule that you want to duplicate among your subdirectory Makefiles in its own file, and use Automake's include feature to incorporate it by reference into each of the subdirectory makefiles.
(source tree root)/subdir-common.am
foo-local:
echo "I'm here! $$(basename $(CURDIR))"
(source tree root)/dir*/Makefile.am
include $(top_srcdir)/subdir-common.am
That doesn't win you much in the example presented, but presumably that's a model of something more complicated that you want to do, where you may benefit from avoiding repetition.
Note that there is nothing special about the .am extension for the name of the included file. I use it for its value as a mnemonic, but it is not inherently meaningful to the Autotools.

How to get case-insensitive completions without Ohmyzsh?

I'm using OHMYZSH but I'm thinking about building a minimal configuration to Zsh.
So here's the problem. Ohmyzsh has special behaviour for completion. For example: if I'm in my Home Directory and type cd mus and hit a TAB it will expand to cd Music, even I'm typing the sentence with small letters.
I've founded this:
Have zsh return case-insensitive auto-complete matches, but prefer exact matches
Which is my problem but on the contrary. I want to get case insensitive without install OHMYZSH.
Looking at the OHMYZSH structure, I've found this:
.
├── ./cache
├── ./CODE_OF_CONDUCT.md
├── ./CONTRIBUTING.md
├── ./custom
├── ./lib
├── ./LICENSE.txt
├── ./log
├── ./oh-my-zsh.sh
├── ./plugins
├── ./README.md
├── ./templates
├── ./themes
└── ./tools
8 directories, 5 files
Inside the lib folder, there are some config files.
lib
├── bzr.zsh
├── clipboard.zsh
├── cli.zsh
├── compfix.zsh
├── completion.zsh
├── correction.zsh
├── diagnostics.zsh
├── directories.zsh
├── functions.zsh
├── git.zsh
├── grep.zsh
├── history.zsh
├── key-bindings.zsh
├── misc.zsh
├── nvm.zsh
├── prompt_info_functions.zsh
├── spectrum.zsh
├── termsupport.zsh
└── theme-and-appearance.zsh
0 directories, 19 files
I've already tried to source the completion.zsh using Zinit(plugin manager for ZSH which enables load some OHMYZSH stuff) without success. I don't know what is the correct file for the behaviour I want.
This is my config:
#exports
export EDITOR=nvim
export VISUAL=code
export SUDO_EDITOR=nvim
# Theme
ZSH_THEME="spaceship"
#PLUGINS
#==============================================================
source "$HOME/.zinit/bin/zinit.zsh"
autoload -Uz _zinit
(( ${+_comps} )) && _comps[zinit]=_zinit
zinit light zdharma/fast-syntax-highlighting
zinit light zsh-users/zsh-autosuggestions
zinit light zsh-users/zsh-completions
zinit light agkozak/zsh-z
#THIS LINE IS MY ATTEMPT TO load OHMYZSH FILES
zinit snippet OMZL::completion.zsh
#==============================================================
### End of Zinit's installer chunk
What's the correct file to load? Or is there another way to get case insensitive completions?
After reading some comments I found a solution. Add these two lines to the Zsh config file:
zstyle ':completion:*' matcher-list '' 'm:{a-zA-Z}={A-Za-z}' 'r:|=*' 'l:|=* r:|=*'
autoload -Uz compinit && compinit

Is there a command to generate all .mo files from multiple .po files?

I have multiple .po files in a standard directory structure of GNU gettext:
locales/
├── en_US
│   └── LC_MESSAGES
│   └── myapp.po
└── zh_TW
└── LC_MESSAGES
└── myapp.po
I knew that I could write a script that uses msgfmt to generate .mo files from these .po files. Something like:
# generate-mo-files.sh
for PO_FILE in locales/*/LC_MESSAGES/*.po
do
MO_FILE="${PO_FILE/.po/.mo}"
msgfmt -o "$MO_FILE" "$PO_FILE"
done
However, writing this script for each project I work on is a bit tedious. Is there a ready-made script for such use case?
If you are using GNU autotools, you can use the script gettextize to prepare your project for gettext. Other build systems have similar special tools.
Otherwise, your little script is exactly the right solution.

Make for identical workflow in separate directories

I'm using make to automate some of my data analysis. I have several directories, each containing a different realization of the data, which consists of several files representing the state of the data at a given time, like so:
├── a
│ ├── time_01.dat
│ ├── time_02.dat
│ ├── time_03.dat
│ └── ...
├── b
│ ├── time_01.dat
│ └── ...
├── c
│ ├── time_01.dat
│ └── ...
├── ...
The number of datafiles in each directory is unknown, and more can be added at any time. The files all have the same naming convention in each directory.
I want to use make to run the exact same set of recipes in each directory (to analyze each dataset separately and uniformly). In particular, there is one script that should run any time a new datafile is added, and creates an output file (analysis_time_XX.txt) for each datafile in the directory. This script does not update any files that have been previously created, but does create all the missing files. Refactoring this script is not a possibility, unfortunately.
So I have one recipe creating many targets, yet it must run separately for each directory. The solutions I've found to create multiple targets with one recipe (e.g. here) do not work in my case, as I need one rule to do this separately for multiple sets of files in separate directories.
These intermediate files are needed for their own sake (as they help validate the data collected), but are also used to create a final comparison plot between the datasets.
My current setup is an ugly combination of functions and .SECONDEXPANSION
dirs = a b c
datafiles = $(foreach dir,$(dirs),$(wildcard $(dir)/*.dat))
df_to_analysis = $(subst .dat,.txt,$(subst time_,analysis_time_,$(1)))
analysis_to_df = $(subst .txt,.dat,$(subst analysis_time_,time_,$(1)))
analysis_files = $(foreach df,$(datafiles),$(call df_to_analysis,$(df)))
all: final_analysis_plot.png
.SECONDEXPANSION:
$(analysis_files): %: $$(call analysis_to_df,%)
python script.py $(dir $#)
final_analysis_plot.png: $(analysis_files)
python make_plot.py $(analysis_files)
Note that script.py creates all of the analysis_time_XX.txt files in the given directory. The flaw with this setup is that make does not know that the first script generates all the targets, and so runs unnecessarily when parallel make is used. For my application parallel make is a necessity, as these scripts have a long runtime, and parallelization saves a lot of time as the setup is "embarrassingly parallel."
Is there an elegant way to fix this issue? Or even an elegant way to clean up the code I have now? I've shown a simple example here, which already requires a good bit of setup, and doing this for several different scripts gets unwieldy quickly.
I think, in your case there's no need to bother with .txt files. If script.py was nicer and could work per-file, there would be a value in writing individual file rules. In this case, we need to introduce an intermediate per-directory .done files.
DATA_DIRS := a b c
# A directory/.done.analysis file means that `script.py` was run here.
DONE_FILES := $(DATA_DIRS:%=%/*.done.analysis)
# .done.analysis depends on all the source data files.
# When a .dat file is added or changes, it will be newer than
# a .done.analysis file; and the analysis would be re-run.
$(DONE_FILES): %/.done.analysis: $(wildcard %/*.dat)
python script.py $(#D)
final_analysis_plot.png: $(DONE_FILES)
python make_plot.py $(wildcard $(DATA_DIRS)/analysis_time_*.txt)

Resources