Portable makefile creation of directories

馋奶兔 提交于 2019-12-01 10:26:58

Windows mkdir always does what Unix mkdir does with the -p switch on. And you can deal with the backslash problem with $(subst). So, on Windows, you want this:

$(BIN_DIR) $(OBJ_DIR):
        mkdir $(subst /,\\,$@)

and on Unix you want this:

$(BIN_DIR) $(OBJ_DIR):
        mkdir -p -- $@

Choosing between these is not practical to do within a makefile. This is what Autoconf is for.

As a side note, never, ever use the @command feature in your makefiles. There will come a day when you need to debug your build process on a machine you do not have direct access to, and on that day, you will regret it.

Samuel

I don't know autoconf. Every experience I've had with it has been tedious. The problem with zwol's solution is that on Windows mkdir returns an error, unlike mkdir -p on Linux. This could break your make rule. The workaround is to ignore the error with - flag before the command, like this:

-mkdir dir

The problem with this is that make still throws an ugly warning for the user. The workaround for this is to run an "always true" command after the mkdir fails as described here, like this:

mkdir dir || true

The problem with this is that Windows and Linux have different syntax for true.

Anyway, I spent too much time on this. I wanted a make file that worked in both POSIX-like and Windows environments. In the end I came up with the following:

ifeq ($(shell echo "check_quotes"),"check_quotes")
   WINDOWS := yes
else
   WINDOWS := no
endif

ifeq ($(WINDOWS),yes)
   mkdir = mkdir $(subst /,\,$(1)) > nul 2>&1 || (exit 0)
   rm = $(wordlist 2,65535,$(foreach FILE,$(subst /,\,$(1)),& del $(FILE) > nul 2>&1)) || (exit 0)
   rmdir = rmdir $(subst /,\,$(1)) > nul 2>&1 || (exit 0)
   echo = echo $(1)
else
   mkdir = mkdir -p $(1)
   rm = rm $(1) > /dev/null 2>&1 || true
   rmdir = rmdir $(1) > /dev/null 2>&1 || true
   echo = echo "$(1)"
endif

The functions/variables are used like so:

rule:
    $(call mkdir,dir)
    $(call echo,  CC      $@)
    $(call rm,file1 file2)
    $(call rmdir,dir1 dir2)

Rationale for the definitions:

  • mkdir: Fix up the path and ignore any errors.
  • del: In Windows del doesn't delete any files if one of the files is specified to be in a directory that doesn't exist. For example, if you try to delete a set of files and dir/file.c is in the list, but dir doesn't exist, no files will be deleted. This implementation works around that issue by invoking del once for each file.
  • rmdir: Fix up the path and ignore any errors.
  • echo: The output's appearance is preserved and doesn't show the extraneous "" in Windows.

I spent a lot of time on this. Perhaps I would have been better off spending my time learning autoconf.

See also:

  1. OS detecting makefile

I solved the portability problem by creating a Python script called mkdir.py and calling it from the Makefile. A limitation is that Python must be installed, but this is most likely true for any version of UNIX.

#!/usr/bin/env python

# Cross-platform mkdir command.

import os
import sys

if __name__=='__main__':
    if len(sys.argv) != 2:
        sys.exit('usage: mkdir.py <directory>')
    directory = sys.argv[1]
    try:
        os.makedirs(directory)
    except OSError:
        pass
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!