1

I have a project which use makefile to control vagrant, I want to put the vagrant parameter into the makefile, such as cpu, memory, ip, hostname, forwarded_port and the like. I find a way that vagrantfile read yaml file to parameterize vagrantfile. So makefile needs a target to read all the user option variables and write them to config.yaml as key-value pairs.

The sample is as follows

# === BEGIN USER OPTIONS ===
BOX_OS ?= fedora
# Box setup
#BOX_IMAGE
# Disk setup
DISK_COUNT ?= 1
DISK_SIZE_GB ?= 25
# VM Resources
MASTER_CPUS ?= 2
MASTER_MEMORY_SIZE_GB ?= 2
NODE_CPUS ?= 2
NODE_MEMORY_SIZE_GB ?= 2

NODE_COUNT ?= 2
# Network
MASTER_IP ?= 192.168.26.10
NODE_IP_NW ?= 192.168.26.
POD_NW_CIDR ?= 10.244.0.0/16

...
...
# === END USER OPTIONS ===

The echo command does achieve it

# Makefile
envInit:
    @echo "POD_NW_CIDR : \"$(POD_NW_CIDR)\"" > ${FILECWD}/configs.yaml

But too many variables can be too complex.

Is there a way to bulk read variables and their values and write them to a yml file

I would very appreciate it if you guys can tell me how to achieve it that bulk read variables and their values and write them to a yml file.

moluzhui
  • 1,003
  • 14
  • 34
  • Are the user option in any way functional in `make` at all? Or is it just text? – Vroomfondel Sep 30 '19 at 11:35
  • The user option may be used as a variable in a make file, in vagrantfile, or in a vagrant virtual machine, such as the parameters of a python program in the vagrant virtual machine, the IP parameter in vagrantfile, and `vagrant up ${vagrant_name}` – moluzhui Sep 30 '19 at 11:46

3 Answers3

1

Define all user options (along with the default values) as a list, so that they are iterable:

# list of user options with default values
userOptions = \
  BOX_OS=2 \
  DISK_COUNT=1 \
  MASTER_IP=192.168.26.10

# replace each default value with the env value, if any
userOptionValues = $(foreach i, $(userOptions), \
  $(word 1, $(subst =, ,$i))=$(or \
      $($(word 1, $(subst =, ,$i))), $($(word 1, $(subst =, ,$i))), $(word 2, $(subst =, ,$i))))

# write the yaml file
envInit:
# empty the file
    @printf "" > configs.yaml
# write a line for each option
    @for i in $(userOptionValues); do \
        printf "%s : %s\n" "$$(printf $$i | cut -d= -f1)" "$$(printf $$i | cut -d= -f2)" >> configs.yaml; \
    done
flyx
  • 35,506
  • 7
  • 89
  • 126
  • This has the problem that you can't have spaces inside options. – Vroomfondel Sep 30 '19 at 12:18
  • Thank you for your answer to solve this problem. I found another way to implement it, but there is a problem, please help me answer it. Thanks again. Please see the following @flyx content. – moluzhui Sep 30 '19 at 12:41
  • @Vroomfondel Yes. If you want to have values with spaces in them, a Makefile is probably not the right tool to handle them. – flyx Sep 30 '19 at 12:58
0

@flyx Thank you for you answer, your code does work great. But I seem to have found a more convenient way, and I've partially modified it.

printvars:
    @echo$(foreach V,$(sort $(.VARIABLES)), \
        $(if $(filter-out environment% default automatic,$(origin $V)),$(info $V: $($V))))

But there is still a gap between achieving the goal.

# the Makefile test file
FILECWD = $(shell pwd)

# === BEGIN USER OPTIONS ===
CLOUD_IP ?= 192.168.79.222
CLOUD_NAME ?= cloud
CLOUD_CPU ?= 6
CLOUD_MEMORY ?= 8


# === END USER OPTIONS ===

printvars:  
    @echo$(foreach V,$(sort $(.VARIABLES)), \
        $(if $(filter-out environment% default automatic,$(origin $V)),$(info $V: $($V))))

make printvars's output contains a number of other variables

$ make printvars 
.DEFAULT_GOAL: printvars
CLOUD_IP: 192.168.79.222
CLOUD_MEMORY: 8
CLOUD_NAME: cloud
CURDIR: /testmakecreateyml0930
FILECWD: /testmakecreateyml0930
GNUMAKEFLAGS: 
MAKEFILE_LIST:  Makefile
MAKEFLAGS: 
SHELL: /bin/sh

And it can only be printed and not exported to the yaml file.This is only one step away from success.

I would appreciate it if you could help me modify it to achieve my goal

moluzhui
  • 1,003
  • 14
  • 34
  • Well I put the the variables in a list value to avoid this exact problem. You need to tell the Makefile in some way which variables should go in the YAML file and which don't, and this solution doesn't do that. – flyx Sep 30 '19 at 12:45
0

You can write directly to a file with GNUmakes $(file) function:

define newline :=
$(strip)
$(strip)
endef
space := $(strip) $(strip)#
-never-matching := ¥# character 165, this is used as a list element that should never appear as a real element

option-names = $(subst $(-never-matching),,$(filter $(-never-matching)%,$(subst $(-never-matching)$(space),$(-never-matching),$(-never-matching)$(strip $(subst $(newline), $(-never-matching),$1)))))


# define your user options in as many separate parts as you like, spaces and empty lines included:
define USER_OPTIONS +=
a = spaces are no problem

b = "neither nearly all 'other' characters: 8&)("
endef

define USER_OPTIONS +=

c = baz baf
d = foobar
endef

# make all definition make variables verbatim 
$(eval $(USER_OPTIONS))

YAML_FORMAT := $(foreach name,$(call option-names,$(USER_OPTIONS)),$(newline)$(name) : $($(name)))

# write the file. Warning: this happens before any rule is run!
$(file >test.yaml,$(YAML_FORMAT))

$(info $(foreach name,$(call option-names,$(USER_OPTIONS)),<$(name) : $($(name))> ))

The trick lies in the clustering of all relevant user option variables in one multi-line make variable. The function option-names pulls all identifiers from that variable into a separate list.

I took the newline etc. character definitions from the GNUmake table toolkit which has many functions for "programmatic" make.

Vroomfondel
  • 2,704
  • 1
  • 15
  • 29