Makefile: shell command execution - makefile

I have the following simple statement that works without problems in the shell:
if [ -z "$(command -v brew)" ]; then
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
in short: if the program is not found, then install it.
The problem is that I can't convert this construction into a Makefile. So far I understand that the construction itself should look like this:
if [ -z "$(shell command -v brew)" ]; then \
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" \
fi
But how to correctly convert /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" string I do not quite understand. Could you give some tips in this matter?

The dollar signs need to be doubled so make doesn't interpret them. Also there needs to be a semicolon at the end of the bash command to separate it from fi since the backslash will eat the usual newline command separator.
some_target:
if [ -z "$$(command -v brew)" ]; then \
/bin/bash -c "$$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"; \
fi

You need to break it into a couple of items.
Firstly, check for brew:
BREW := $(shell command -v brew)
Then check if the variable is set. If it's not set, then run the installer:
ifeq ($(BREW),)
$(shell bash -c "$$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)")
BREW := $(shell command -v brew)
endif
It's doable in an entire single shell command, but this breaks it into a couple of shell pieces.
the use of := means that the evaluation happens at the time the line is parsed, which is how you get to check and override the value.
In a small, self-contained makefile:
BREW := $(shell command -v brew)
ifeq ($(BREW),)
$(shell bash -c "$$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)")
BREW := $(shell command -v brew)
endif
all:
echo $(BREW)

Related

Makefile to "activate" Python virtual environment

I'm trying to create a Python virtual environment with a Makefile and also activate it once the make command finishes to ease things for the user. Apparently, this is not possible because "a child process can not alter the parent's environment." I was wondering if there's any workaround for this. This is part of my Makefile so far:
.PHONY: create-venv venv
.DEFAULT_GOAL := all
SHELL=/bin/bash
CPUTYPE = $(shell uname -m | sed "s/\\ /_/g")
SYSTYPE = $(shell uname -s)
BUILDDIR = build/$(SYSTYPE)-$(CPUTYPE)
VENV_NAME?=venv
VENV_DIR=$(BUILDDIR)/${VENV_NAME}
VENV_BIN=$(shell pwd)/${VENV_DIR}/bin
VENV_ACTIVATE=. ${VENV_BIN}/activate
PYTHON=${VENV_BIN}/python3
create-venv:
test -d $(BUILDDIR) || mkdir -p $(BUILDDIR)
which python3 || apt install -y python3 python3-pip
test -d $(VENV_DIR) || python3 -m venv $(VENV_DIR)
venv: ${VENV_BIN}/activate
${VENV_BIN}/activate: setup.py
test -d $(VENV_DIR) || make create-venv
${PYTHON} -m pip install -r requirements.txt
touch $(VENV_BIN)/activate
source ${VENV_BIN}/activate # <- doesn't work
. ${VENV_BIN}/activate # <- doesn't work either
You can activate the environment and run a shell in the activated env:
. ${VENV_BIN}/activate && exec bash
(Please note it must be in one line to be run in one shell; exec is used to replace the shell with a new one.)
When you finish working with the environment you exit and then the Makefile is finished.
You could do something like this.
It relies on your looking at the activate script and seeing what env vars it sets, so it is totally ugly.
$(eval $(shell source $(PYTHON3_VENV)/bin/activate && echo "export PATH := $$PATH; export PYTHONHOME := $$PYTHONHOME; export VIRTUAL_ENV := $$VIRTUAL_ENV" ))

Setup a global variable dynamically in a Makefile

I have the following code in a Makefile, which execute one sequence of command or another based on a environmental variable.
generate :
if test -z "$$VIRTUAL_ENV"; then \
$(PYTHON) -m fades -V &>/dev/null || $(PYTHON) -m pip install --user fades; $(PYTHON) -m fades -r requirements.txt script.py;"; \
else \
python -m pip install -r requirements.txt && python script.py; \
fi
It works as expected, but I would like to do the same thing on multiple targets, to use it on other files, without having to copy this snippet of code multiple times.
My idea would be to set a variable dynamically (based on the condition that has been evaluated), containing the one command or the other, to be used over and over again, like alias in Bash.
Is that a good idea? Is it possible to set a global alias in the Makefile so it can choose between two Python interpreters based on an environmental variable?
Assuming you're using GNU make, you can do it like this:
ifdef VIRTUAL_ENV
PYCMD = python -m pip install -r requirements.txt && python
else
PYCMD = $(PYTHON) -m fades -V >/dev/null 2>&1 || $(PYTHON) -m pip install --user fades; $(PYTHON) -m fades -r requirements.txt
endif
generate:
$(PYCMD) script.py
Note I changed &>/dev/null to >/dev/null 2>&1 because the former is a bash-only feature and is not valid in POSIX sh, and make (by default) always runs /bin/sh which is (on many systems) a POSIX sh.
I don't know why you're using python in one section and $(PYTHON) in the other; it seems like you'd want to use the same in both but anyway.

Install homebrew using Makefile

I'm trying to install Homebrew using a Makefile, the contents of the Makefile is this:
.PHONY: install
install:
# Install homebrew
/usr/bin/ruby -e $(shell curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)
However this just prints the entire contents of the script, but it does not execute anything. I got as far as googling for this issue and seeing that the characters $, (, ) have a special meaning in a Makefile, however I could not find any solution.
try just piping output from curl to ruby like this:
.PHONY: install
install:
# Install homebrew
curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install | ruby
If you encounter the following warning:
Warning: The Ruby Homebrew install is now deprecated and has been rewritten in Bash.
Please migrate to the following command:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
You can modify #igagis answer as such
.PHONY:install
install:
curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh | bash
I was running into needing sudo, then sudo telling me not to run as root so the command i used was:
sudo true
curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | sudo -u $$USER bash
Notes:
I use sudo true to run sudo and do nothing. that way the user can input their password. running sudo make from the terminal caused the $USER to be root. if you remove the sudo true line, then if sudo has not been run yet, the sudo access check will fail and not let you insert a password when running the curl line
the two $ makes the makefile write a literal $

Bypassing prompt (to press return) in homebrew install script

Very simple script that installs homebrew:
#!/bin/bash
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
The output gives:
==> This script will install:
/usr/local/bin/brew
/usr/local/Library/...
/usr/local/share/man/man1/brew.1
Press RETURN to continue or any other key to abort
How do I press enter in a script like this? Would expect be the best route?
Reading the source of https://raw.github.com/Homebrew/homebrew/go/install -- it only prompts if stdin is a TTY. If you redirect stdin from /dev/null, it won't prompt at all. So:
ruby \
-e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" \
</dev/null
This is what yes is for:
yes '' | ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
Per the lead maintainer of Homebrew:
echo | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
this will work
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" < /dev/null
This works fine for me,
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null

Unattended (no-prompt) Homebrew installation using expect

According to the Homebrew installation instructions, the following command can be used to install:
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
This works, but it needs user input two times; to confirm the install and in a sudo prompt invoked by the script:
$ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
==> This script will install:
/usr/local/bin/brew
/usr/local/Library/...
/usr/local/share/man/man1/brew.1
Press RETURN to continue or any other key to abort
==> /usr/bin/sudo /bin/mkdir /usr/local
Password:
Homebrew doesn't have arguments for unattended installations, so the only option I can think of is to programatically input the expected data. I tried using expect, but I can't quite get the syntax right:
$ expect -c 'spawn ruby -e \"\$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)\";expect "RETURN";send "\n"'
ruby: invalid option -f (-h will show valid options) (RuntimeError)
send: spawn id exp7 not open
while executing
"send "\n""
What am I doing wrong?
Unattended installation is now officially supported
https://docs.brew.sh/Installation#unattended-installation
If you want to create a setup script which installs homebrew silently then just pipe a blank echo to the homebrew's installer. Then redirect the results to /dev/null as #charles-duffy suggested.
#!/usr/bin/env bash
# install.sh
URL_BREW='https://raw.githubusercontent.com/Homebrew/install/master/install'
echo -n '- Installing brew ... '
echo | /usr/bin/ruby -e "$(curl -fsSL $URL_BREW)" > /dev/null
if [ $? -eq 0 ]; then echo 'OK'; else echo 'NG'; fi
$ ./install.sh

Resources