From af361f846a7edf1d06a86fd8fd3573e574b11ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B5ivo=20Leedj=C3=A4rv?= Date: Tue, 17 Dec 2024 10:56:30 +0100 Subject: [PATCH 1/4] Makefile: Move conditionals into separate script There is no portable syntax for conditionals and other more complex constructs, including shell scripts. This patch moves non-portable conditionals and other such parts into a separate script written in OCaml. The script itself is fully portable, it does not require GNU tools or even a POSIX shell. With this patch the main makefile becomes fully portable and currently works with GNU Make, BSD make, Solaris make and NMAKE. --- Makefile | 103 +++----- src/.depend | 2 + src/Makefile | 86 +++---- src/Makefile.OCaml | 313 +++-------------------- src/dune | 2 +- src/make_tools.ml | 620 +++++++++++++++++++++++++++++++++++++++++++++ src/uimac/Makefile | 4 - 7 files changed, 745 insertions(+), 385 deletions(-) create mode 100644 src/make_tools.ml diff --git a/Makefile b/Makefile index b61786daa..74a282940 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,15 @@ # Unison file synchronizer: Makefile # See LICENSE for terms. +# IMPORTANT! +# +# This file is portable and compatible with GNU Make, BSD make, Solaris +# (d)make and NMAKE. Do not make any changes in this file unless you are +# certain that the changes do not break this portability. + +default: all +.PHONY: default + # Sub-makefiles are perfectly fine for parallel builds. # This makefile is not, due to recursive invocations of make. .NOTPARALLEL: @@ -9,85 +18,49 @@ all: src manpage .PHONY: src -src: - $(MAKE) -C src +src: FRC + cd src && $(MAKE) +FRC: ; +# Not all make seem to work without FRC, even with .PHONY + +.PHONY: tui gui macui fsmonitor depend +tui gui macui fsmonitor depend: + cd src && $(MAKE) $@ # It's a wart that docs need "unison" built (vs some docs utility). .PHONY: docs docs: src manpage - $(MAKE) -C doc + cd doc && $(MAKE) # "src" is a prerequisite to prevent parallel build errors. # manpage builds currently require a pre-built "unison" binary. .PHONY: manpage manpage: src - $(MAKE) -C man + cd man && $(MAKE) .PHONY: test -test: ./src/unison - ./src/unison -ui text -selftest - -.PHONY: depend -depend: - $(MAKE) -C src depend +test: + ocaml src/make_tools.ml run ./src/unison -ui text -selftest +# Note: unison binary is not built automatically for the test target, +# so as to avoid building it with unwanted configuration. +# This construct is here to remain compatible with make implementations +# that do not have the -C option, work with different (also non-POSIX) +# shells, and not rely on single shell per line execution. .PHONY: clean -clean: - $(MAKE) -C doc clean - $(MAKE) -C man clean - $(MAKE) -C src clean - -INSTALL ?= install -INSTALL_PROGRAM ?= $(INSTALL) -INSTALL_DATA ?= $(INSTALL) -m 644 +clean: clean_doc clean_man clean_src +.PHONY: clean_doc +clean_doc: + cd doc && $(MAKE) clean +.PHONY: clean_man +clean_man: + cd man && $(MAKE) clean +.PHONY: clean_src +clean_src: + cd src && $(MAKE) clean prefix = /usr/local -PREFIX ?= $(prefix) -exec_prefix = $(PREFIX) -EXEC_PREFIX ?= $(exec_prefix) -bindir = $(EXEC_PREFIX)/bin -BINDIR ?= $(bindir) -datarootdir = $(PREFIX)/share -DATAROOTDIR ?= $(datarootdir) -mandir = $(DATAROOTDIR)/man -MANDIR ?= $(mandir) -man1dir = $(MANDIR)/man1 -MAN1DIR ?= $(man1dir) -manext = .1 -MANEXT ?= $(manext) .PHONY: install -install: all - $(INSTALL) -d "$(DESTDIR)$(BINDIR)" -ifneq ($(wildcard src/unison),) - $(INSTALL_PROGRAM) src/unison "$(DESTDIR)$(BINDIR)/unison" -endif -ifneq ($(wildcard src/unison-gui),) - $(INSTALL_PROGRAM) src/unison-gui "$(DESTDIR)$(BINDIR)/unison-gui" -endif -ifneq ($(wildcard src/unison-fsmonitor),) - $(INSTALL_PROGRAM) src/unison-fsmonitor "$(DESTDIR)$(BINDIR)/unison-fsmonitor" -else - ifneq ($(wildcard src/fsmonitor.py),) - $(INSTALL_PROGRAM) src/fsmonitor.py "$(DESTDIR)$(BINDIR)/fsmonitor.py" - endif -endif -ifneq ($(wildcard man/unison.1),) - $(INSTALL) -d "$(DESTDIR)$(MAN1DIR)" - $(INSTALL_DATA) man/unison.1 "$(DESTDIR)$(MAN1DIR)/unison$(MANEXT)" -endif -ifneq ($(wildcard src/uimac/build/Default/Unison.app),) - $(info !!! The GUI for macOS has been built but will NOT be installed automatically. \ - You can find the built GUI package at $(abspath src/uimac/build/Default/Unison.app)) -endif - -# Delegate other targets to the sub-makefile -.PHONY: Makefile - -./src/%: - $(MAKE) -C src $* - -%: FORCE - $(MAKE) -C src $@ -.PHONY: FORCE -FORCE: ; +install: + ocaml src/make_tools.ml install diff --git a/src/.depend b/src/.depend index 1e3affcfd..499d77d2c 100644 --- a/src/.depend +++ b/src/.depend @@ -628,6 +628,8 @@ main.cmx : \ remote.cmx \ ubase/prefs.cmx \ os.cmx +make_tools.cmo : +make_tools.cmx : name.cmo : \ ubase/util.cmi \ ubase/umarshal.cmi \ diff --git a/src/Makefile b/src/Makefile index 02ccf4f87..734ef9854 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,39 +1,55 @@ # Unison file synchronizer: src/Makefile -# Copyright 1999-2023 (see ../LICENSE for terms). +# Copyright 1999-2024 (see ../LICENSE for terms). -## User Settings - -# Set NATIVE=false if you are not using the native code compiler (ocamlopt) -# This is not advised, though: Unison runs much slower when byte-compiled. -# Set NATIVE=true to force a native build, should auto-detection fail. -NATIVE=auto +# IMPORTANT! +# +# This file is portable and compatible with GNU Make, BSD make, Solaris +# (d)make and NMAKE. Do not make any changes in this file unless you are +# certain that the changes do not break this portability. ######################################################################## ######################################################################## # (There should be no need to change anything from here on) ## ######################################################################## -###################################################################### -# Building installation instructions - -.PHONY: all -all: tui guimaybe macuimaybe fsmonitor - -######################################################################## -## Miscellaneous developer-only switches -# [2023-05] This check is here only temporarily for a smooth transition -ifeq ($(STATIC), true) - $(error Variable STATIC is no longer in use. Please set appropriate LDFLAGS,\ - like -static, instead) -endif - -# NAME, VERSION, and MAJORVERSION, automatically generated --include Makefile.ProjectInfo - -######################################################################## -### Compilation rules +default: all +.PHONY: default + +# Executed only by BSD make +.BEGIN: + @touch _mk.cfg + @ocaml make_tools.ml conf > Makefile.cfg + @ocaml make_tools.ml conf2 > Makefile2.cfg + +# Executed only by Solaris dmake +.INIT: + @touch _mk.cfg + @ocaml make_tools.ml conf ASSUME_DMAKE=true > Makefile.cfg + @ocaml make_tools.ml conf2 ASSUME_DMAKE=true > Makefile2.cfg + +# Executed only by GNU Make and NMAKE +Makefile.cfg: _mk.cfg + @ocaml make_tools.ml conf > Makefile.cfg + @ocaml make_tools.ml conf2 MAKE="$(MAKE)" > Makefile2.cfg +_mk.cfg: ; + +TARGETS = all tui gui macui fsmonitor clean depend dependgraph paths +FILES = unison unison.exe + +.NOTPARALLEL: +.PHONY: $(TARGETS) +$(TARGETS) $(FILES): Makefile.cfg + $(MAKE) -f Makefile.OCaml $@ + +# Though not ideal, recursive make is needed here, in the name of portability. +# +# Directly including the freshly-generated Makefile.cfg could work with GNU +# Make (when the target filename in the rule is guarded by $(MAKE_RESTARTS) +# to prevent gmake from going into infinite loop), but fails with all other +# make implementations. Solaris make, BSD make and NMAKE all read Makefile.cfg +# before generating it (failing when *.cfg does not exist, reading stale +# contents otherwise). "-include" instead of "include" is not portable. -include Makefile.OCaml ###################################################################### # For developers @@ -55,19 +71,3 @@ tags: ; fi TAGS: tags - -###################################################################### -# Misc - -.PHONY: clean - -#################################################################### -# Documentation strings - -# Cons up a fake strings.ml if necessary (the real one is generated when -# we build the documentation, but we need to be able to compile the -# executable here to do that!) -strings.ml: - echo "(* Dummy strings.ml *)" > strings.ml - echo "let docs = []" >> strings.ml - diff --git a/src/Makefile.OCaml b/src/Makefile.OCaml index 312926a62..3f79547bb 100644 --- a/src/Makefile.OCaml +++ b/src/Makefile.OCaml @@ -1,250 +1,51 @@ # Unison file synchronizer: src/Makefile.OCaml # See ../LICENSE for terms. -#################################################################### -# Makefile rules for compiling ocaml programs # -#################################################################### +# IMPORTANT! +# +# This file is portable and compatible with GNU Make, BSD make, Solaris +# (d)make and NMAKE. Do not make any changes in this file unless you are +# certain that the changes do not break this portability. -#################################################################### -### Compilers +all: tui guimaybe macuimaybe fsmonitor +.PHONY: all # This .PHONY definition must come after the first target, + # so that make implementations which don't support .PHONY + # don't treat it as the default target (which in turn + # would cause _all_ targets to be made). +.PHONY: clean + +# NAME, VERSION, and MAJORVERSION +include Makefile.ProjectInfo -OCAMLC=$(TOOL_PREFIX)ocamlc -OCAMLOPT=$(TOOL_PREFIX)ocamlopt +# Automatically generated configuration +include Makefile.cfg #################################################################### -### Try to automatically guess OS - -ifeq (${OSCOMP},cross) # Cross-compilation under Linux - TOOL_PREFIX=x86_64-w64-mingw32- -endif - -# Cygwin is for doing POSIX builds in Windows. -# MinGW is for doing native Windows builds in a minimal POSIX environment. -# MSVC is for native Windows builds without POSIX env (yet this Makefile -# requires a minimal POSIX environment anyway). -OCAML_OSTYPE:=$(shell $(OCAMLC) -config-var os_type) - -ifeq (${OCAML_OSTYPE},Win32) # Native Windows build - OSARCH=Win32 -else ifeq (${OCAML_OSTYPE},Cygwin) # POSIX build for Windows - OSARCH=Cygwin -else - OSARCH:=$(shell uname) - # Darwin is reported for macOS - # SunOS is reported for Solaris, OpenSolaris and derivatives (illumos) -endif +# Makefile rules for compiling ocaml programs # +#################################################################### #################################################################### ### Try to automatically guess UI style -ifneq ($(strip $(UISTYLE)),) - $(error UISTYLE is no longer used. See build instructions) -endif - -OCAMLLIBDIR:=$(shell $(OCAMLC) -config-var standard_library) - -# For Windows, an additional UI style modifier is available, `UI_WINOS` -# Legal values are -# UI_WINOS= # *default*; builds unison purely as a Windows console ('text') or GUI ('gtk3') application -# UI_WINOS=hybrid # (with UISTYLE=gtk3) builds unison as a hybrid application (GUI application attached to a text console) -# * ref: -# -OCAMLFIND := $(shell command -v ocamlfind 2> /dev/null) - -ifdef OCAMLFIND - ifneq ($(strip $(shell $(OCAMLFIND) query lablgtk3 2> /dev/null)),) - HAS_LABLGTK3=true - endif -else - LABLGTK3LIB=$(OCAMLLIBDIR)/lablgtk3 - ifeq ($(wildcard $(LABLGTK3LIB)),$(LABLGTK3LIB)) - HAS_LABLGTK3=true - endif -endif - .PHONY: guimaybe -ifeq ($(HAS_LABLGTK3), true) - guimaybe: gui -else - guimaybe: - $(info GUI library lablgtk not found. GTK GUI will not be built.) -endif +guimaybe: .PHONY: macuimaybe -ifeq ($(OSARCH), Darwin) - # If XCode is not installed, xcodebuild is just a placeholder telling that XCode is not installed - # and any invocation of xcodebuild results in a non 0 exit code. - ifeq ($(shell xcodebuild -version > /dev/null; echo $$?), 0) - macuimaybe: macui - else - macuimaybe: - $(info Not building macOS native GUI because XCode is not installed.) - endif -else - macuimaybe: - $(info Not on macOS. macOS native GUI will not be built.) -endif +macuimaybe: #################################################################### ### Default parameters -.PHONY: buildinfodebug -buildinfodebug: - $(info $(building_for)) - $(info NATIVE = $(NATIVE)) - -buildexecutable: buildinfodebug - -# Generate backtrace information for exceptions -CAMLFLAGS+=-g - -ifneq ($(strip $(CFLAGS)),) - CAMLCFLAGS+=-ccopt '$(CFLAGS)' -endif -ifneq ($(strip $(CPPFLAGS)),) - CAMLCFLAGS+=-ccopt '$(CPPFLAGS)' -endif -ifneq ($(strip $(LDFLAGS)),) - CAMLLDFLAGS+=-cclib '$(LDFLAGS)' -endif -ifneq ($(strip $(LDLIBS)),) - CLIBS+=-cclib '$(LDLIBS)' -endif - INCLFLAGS = -I lwt -I ubase -I system -I system/$(SYSTEM) -I lwt/$(SYSTEM) DEP_INCLFLAGS = -I lwt -I ubase -I system \ -I fsmonitor -I fsmonitor/inotify -I fsmonitor/solaris -I fsmonitor/windows -CAMLFLAGS+=$(INCLFLAGS) - -# The messy situation requiring the use of OUTPUT_SEL was fixed in OCaml 4.13. -# All usages of OUTPUT_SEL should be removed when 4.13 becomes a requirement. -ifeq ($(OSARCH),Win32) - # Native Win32 build - EXEC_EXT=.exe - ifeq ($(shell $(OCAMLC) -config-var ext_obj),.obj) - OBJ_EXT=.obj - else - OBJ_EXT=.o - endif - ifeq ($(shell $(OCAMLC) -config-var ccomp_type),msvc) - OUTPUT_SEL=-Fo - CLIBS+=-cclib user32.lib -cclib "-link win32rc/unison.res" - buildexecutable: win32rc/unison.res - else - OUTPUT_SEL=-o - CLIBS+=-cclib "-link win32rc/unison.res.lib" - buildexecutable: win32rc/unison.res.lib - endif - # Make Windows GUI come up with no console window - ifneq ($(UI_WINOS), hybrid) - LDFLAGS_GUI+=-subsystem windows - # -subsystem is a flexlink arg; - # respective gcc args: -mwindows or -Wl,--subsystem,windows - # respective MSVC linker arg: /SUBSYSTEM:WINDOWS - endif - CWD=. - WINCOBJS=system/system_win_stubs$(OBJ_EXT) lwt/lwt_unix_stubs$(OBJ_EXT) - WINOBJS=system/system_win.cmo - SYSTEM=win - building_for = Building for Windows -else - # Unix build, or Cygwin POSIX (GNU C) build - OBJ_EXT=.o - OUTPUT_SEL="-o " - WINOBJS= - SYSTEM=generic - # This is not strictly necessary as Cygwin can do a generic Unix build - # (and is actually meant to). - ifeq ($(OSARCH),Cygwin) - CWD=. - EXEC_EXT=.exe - CLIBS+=-cclib win32rc/unison.res.lib - buildexecutable: win32rc/unison.res.lib - building_for = Building for Cygwin - else - CWD=$(shell pwd) - EXEC_EXT= - # openpty is in the libutil library - ifneq ($(OSARCH),SunOS) - ifneq ($(OSARCH),Darwin) - CLIBS+=-cclib -lutil - endif - endif - ifeq ($(OSARCH),SunOS) - # ACL functions - CLIBS+=-cclib -lsec - endif - building_for = Building for Unix - endif -endif ubase/projectInfo.ml: Makefile.ProjectInfo - echo 'let myName = "'$(NAME)'";;' > $@ - echo 'let myVersion = "'$(VERSION)'";;' >> $@ - echo 'let myMajorVersion = "'$(MAJORVERSION)'";;' >> $@ + ocaml make_tools.ml projectinfo NAME="$(NAME)" VERSION="$(VERSION)" MAJORVERSION="$(MAJORVERSION)" > $@ clean:: cd ubase && $(RM) projectInfo.ml -#################################################################### -### Compilation boilerplate - -ifeq ($(NATIVE), auto) - ifneq ($(strip $(shell command -v $(OCAMLOPT) 2> /dev/null)),) - NATIVE=true - else - NATIVE=false - endif -endif - -ifeq ($(NATIVE), true) - ## Set up for native code compilation - - CAMLC=$(OCAMLOPT) - - CAMLOBJS = $(OCAMLOBJS:.cmo=.cmx) - CAMLOBJS_TUI = $(OCAMLOBJS_TUI:.cmo=.cmx) - CAMLOBJS_GUI = $(OCAMLOBJS_GUI:.cmo=.cmx) - CAMLOBJS_MAC = $(OCAMLOBJS_MAC:.cmo=.cmx) - CAMLOBJS_FSM = $(FSMOCAMLOBJS:.cmo=.cmx) - - CAMLLIBS = $(OCAMLLIBS:.cma=.cmxa) - CAMLLIBS_GUI = $(OCAMLLIBS_GUI:.cma=.cmxa) - CAMLLIBS_MAC = $(OCAMLLIBS_MAC:.cma=.cmxa) - CAMLLIBS_FSM = $(FSMOCAMLLIBS:.cma=.cmxa) - -else - ## Set up for bytecode compilation - - CAMLC=$(OCAMLC) - # -output-complete-exe is available since OCaml 4.10 - # OCaml > 5.2.0 no longer supports detection of compiler options, - # hence the hack of comparing the output to -version. - ifneq ($(strip $(shell $(OCAMLC) -output-complete-exe -version 2>&1)), $(strip $(shell $(OCAMLC) -version))) - CAMLLDFLAGS+=-custom - else - CAMLLDFLAGS+=-output-complete-exe # can safely strip the binary - endif - - CAMLOBJS = $(OCAMLOBJS) - CAMLOBJS_TUI = $(OCAMLOBJS_TUI) - CAMLOBJS_GUI = $(OCAMLOBJS_GUI) - CAMLOBJS_MAC = $(OCAMLOBJS_MAC) - CAMLOBJS_FSM = $(FSMOCAMLOBJS) - - CAMLLIBS = $(OCAMLLIBS) - CAMLLIBS_GUI = $(OCAMLLIBS_GUI) - CAMLLIBS_MAC = $(OCAMLLIBS_MAC) - CAMLLIBS_FSM = $(FSMOCAMLLIBS) - -endif - -OCAML_C_COMPILER := $(shell $(OCAMLC) -config-var c_compiler) -C_COMPILER := $(or ${OCAML_C_COMPILER},${CC}) -C_COMPILER_PREFIX := $(C_COMPILER:gcc=) -WINDRES := $(or ${TOOL_PREFIX},$(filter i686-w64-mingw32- x86_64-w64-mingw32-,$(C_COMPILER_PREFIX:gcc${EXEC_EXT}=)))windres -##$(info windres='${WINDRES}') - #################################################################### ### Unison objects and libraries @@ -300,24 +101,17 @@ OCAMLLIBS_MAC = threads.cma CAMLFLAGS_MAC = -I +threads ## Graphic UI -ifndef OCAMLFIND - CAMLFLAGS_GUI = -I +lablgtk3 -I +cairo2 -else - CAMLFLAGS_GUI = $(shell $(OCAMLFIND) query -format "-I '%d'" lablgtk3 ) \ - $(shell $(OCAMLFIND) query -format "-I '%d'" cairo2 ) -endif OCAMLOBJS_GUI = pixmaps.cmo uigtk3.cmo linkgtk3.cmo OCAMLLIBS_GUI = lablgtk3.cma cairo.cma -ifneq ($(strip $(LDFLAGS_GUI)),) - CAMLLDFLAGS_GUI+=-cclib "$(LDFLAGS_GUI)" -endif - #################################################################### ### Unison executables NAME_GUI = $(NAME)-gui +.PHONY: buildexecutable +buildexecutable: + .PHONY: tui tui: buildexecutable $(NAME)$(EXEC_EXT) @@ -329,56 +123,29 @@ macui: buildexecutable macexecutable .PHONY: macexecutable macexecutable: $(NAME)-blob.o - cd uimac && $(MAKE) macexecutable VERSION="$(VERSION)" OCAMLLIBDIR="$(OCAMLLIBDIR)" + cd uimac && $(MAKE) macexecutable VERSION="$(VERSION)" OCAMLLIBDIR="$(OCAMLLIBDIR)" XCODEFLAGS="$(XCODEFLAGS)" clean:: - cd uimac && $(MAKE) clean + -cd uimac && $(MAKE) clean #################################################################### ### Filesystem monitoring NAME_FSM = $(NAME)-fsmonitor -ifeq ($(OSARCH),Linux) - FSMDIR = fsmonitor/inotify -endif - -ifneq ($(findstring $(OSARCH),FreeBSD OpenBSD NetBSD DragonFly),) - LIBINOTIFY_LIB:=-cclib '$(shell pkg-config --libs libinotify 2> /dev/null || printf ' -linotify')' - LIBINOTIFY_INC:=-ccopt '$(shell pkg-config --cflags libinotify 2> /dev/null)' - FOUND_LIBINOTIFY := $(shell { printf '' > inotifytest__.ml ;\ - $(CAMLC) $(CAMLCFLAGS) $(CAMLLDFLAGS) $(LIBINOTIFY_LIB) \ - -o inotifytest__ inotifytest__.ml > /dev/null 2>&1 && printf true ; } ;\ - rm -f inotifytest__.ml inotifytest__.cm[oix] inotifytest__.o inotifytest__ > /dev/null 2>&1) - ifeq ($(FOUND_LIBINOTIFY), true) - FSMDIR = fsmonitor/inotify - $(FSMCOBJS): CAMLCFLAGS_FSM_X = $(LIBINOTIFY_INC) - CLIBS_FSM = $(LIBINOTIFY_LIB) - endif -endif - -ifeq ($(OSARCH),SunOS) - FSMDIR = fsmonitor/solaris -endif - -ifeq ($(OSARCH),Win32) - FSMDIR = fsmonitor/windows -endif - .PHONY: fsmonitor -ifdef FSMDIR - fsmonitor: buildexecutable $(NAME_FSM)$(EXEC_EXT) - -include $(FSMDIR)/Makefile -else - fsmonitor: - $(info fsmonitor implementation is not available or not configured for this system. fsmonitor will not be built.) -endif +fsmonitor: + +.PHONY: fsmonitorexecutable +fsmonitorexecutable: buildexecutable $(NAME_FSM)$(EXEC_EXT) + +CAMLFLAGS_FSM = -I fsmonitor -I ./$(FSMDIR) #################################################################### ### Dependencies # Include an automatically generated list of dependencies --include .depend +include .depend # Additional dependencies depending on the system system.cmo fspath.cmo fs.cmo: system/$(SYSTEM)/system_impl.cmo system.cmx fspath.cmx fs.cmx: system/$(SYSTEM)/system_impl.cmx @@ -438,21 +205,22 @@ CAMLCFLAGS_X = $(CAMLCFLAGS) $(CAMLCFLAGS_FSM_X) @echo "$(CAMLC): $< ---> $@" $(CAMLC) $(CAMLFLAGS_X) $(CAMLCFLAGS_X) -ccopt $(OUTPUT_SEL)$(CWD)/$@ -c $(CWD)/$< +# The second part of configuration must be included here because rule lines +# are expanded at parsing, so we need all variables to be defined by then. +include Makefile2.cfg + $(NAME)$(EXEC_EXT): $(CAMLOBJS) $(CAMLOBJS_TUI) $(COBJS) @echo Linking $@ - $(CAMLC) -verbose $(CAMLFLAGS_X) $(CAMLLDFLAGS) -o $@ $(CAMLLIBS) $^ $(CLIBS) + $(CAMLC) -verbose $(CAMLFLAGS_X) $(CAMLLDFLAGS) -o $@ $(CAMLLIBS) $(ALL__SRC) $(CLIBS) -$(NAME_GUI)$(EXEC_EXT) $(CAMLOBJS_GUI): CAMLFLAGS_GUI_X = $(CAMLFLAGS_GUI) $(NAME_GUI)$(EXEC_EXT): $(CAMLOBJS) $(CAMLOBJS_GUI) $(COBJS) @echo Linking $@ - $(CAMLC) -verbose $(CAMLFLAGS_X) $(CAMLLDFLAGS) $(CAMLLDFLAGS_GUI) -o $@ $(CAMLLIBS) $(CAMLLIBS_GUI) $^ $(CLIBS) + $(CAMLC) -verbose $(CAMLFLAGS_X) $(CAMLLDFLAGS) $(CAMLLDFLAGS_GUI) -o $@ $(CAMLLIBS) $(CAMLLIBS_GUI) $(ALL__SRC) $(CLIBS) -$(NAME_FSM)$(EXEC_EXT) $(CAMLOBJS_FSM) $(FSMOCAMLOBJS:.cmo=.cmi): CAMLFLAGS_FSM_X = -I fsmonitor -I $(FSMDIR) $(NAME_FSM)$(EXEC_EXT): $(CAMLOBJS_FSM) $(FSMCOBJS) @echo Linking $@ - $(CAMLC) -verbose $(CAMLFLAGS_X) $(CAMLLDFLAGS) -o $@ $(CAMLLIBS_FSM) $^ $(CLIBS) $(CLIBS_FSM) + $(CAMLC) -verbose $(CAMLFLAGS_X) $(CAMLLDFLAGS) -o $@ $(CAMLLIBS_FSM) $(ALL__SRC) $(CLIBS) $(CLIBS_FSM) -$(NAME)-blob.o $(CAMLOBJS_MAC): CAMLFLAGS_MAC_X = $(CAMLFLAGS_MAC) # Unfortunately -output-obj does not put .o files into the output, only .cmx # files, so we have to use $(LD) to take care of COBJS. # [2023-07] The limitations of -output-obj have been overcome by a new option @@ -471,6 +239,7 @@ $(NAME)-blob.o: $(CAMLOBJS) $(CAMLOBJS_MAC) $(COBJS) RM_FILES = $(RM) *.cmi *.cmo *.cmx *.cma *.cmxa *.o *.obj *.lib *.exp *~ .*~ *.bak *.tmp clean:: + -$(RM) Makefile.cfg Makefile2.cfg _mk.cfg -$(RM) $(NAME) $(NAME).exe -$(RM) $(NAME_GUI) $(NAME_GUI).exe -$(RM) $(NAME_FSM) $(NAME_FSM).exe diff --git a/src/dune b/src/dune index 06d60786f..a8ee5c4e1 100644 --- a/src/dune +++ b/src/dune @@ -1,7 +1,7 @@ (library (name unison_lib) (wrapped false) - (modules :standard \ linktext linkgtk3 uigtk3 pixmaps uimacbridge test) + (modules :standard \ linktext linkgtk3 uigtk3 pixmaps uimacbridge test make_tools) (modules_without_implementation ui) (flags :standard -w -3-6-9-10-26-27-32-34-35-38-39-50-52 diff --git a/src/make_tools.ml b/src/make_tools.ml new file mode 100644 index 000000000..b6ab3dba5 --- /dev/null +++ b/src/make_tools.ml @@ -0,0 +1,620 @@ +#directory "+unix";; +#load "unix.cma";; + +module type TOOLS = sig + module Env : Map.S with type key = string + val env : string Env.t (* Environment variables *) + val args : string Env.t (* Command line argument variables *) + val inputs : string Env.t (* [env] + [args], with [args] taking priority *) + val vars : string Env.t ref (* Internally defined variables *) + val ( .$() ) : string Env.t -> string -> string (* Get value from any map *) + val ( $ ) : string -> string (* Value from [vars] with fallback to [inputs] *) + val ( <-- ) : string -> string -> unit (* Set value in [vars] *) + val ( <-+= ) : string -> string -> unit (* Append to value in [vars] (defaults to value in [inputs]) *) + val ( <--? ) : string -> string -> string (* Copy from [inputs] to [vars], or set a default *) + val shell : ?err_null:bool -> string -> string + val get_command : string -> string option + val is_empty : string -> bool + val not_empty : string -> bool + val exists : string -> string -> bool + val info : string -> unit + val error : string -> 'a +end +module Make = functor (Tools : TOOLS) -> struct +open Tools + +let output = ref [] +let outp s = output := s :: !output + +(* [2023-05] This check is here only temporarily for a smooth transition *) +let () = + if inputs.$("STATIC") = "true" then + error "Variable STATIC is no longer in use. Please set appropriate \ + LDFLAGS, like -static, instead. See build instructions." + +(********************************************************************* +*** Compilers ***) + +let tool_prefix = "TOOL_PREFIX" <--? + if inputs.$("OSCOMP") = "cross" then "x86_64-w64-mingw32-" else "" + (* FIXME: OSCOMP is a legacy argument; probably not used by anyone *) + +let ocamlc = "OCAMLC" <--? tool_prefix ^ "ocamlc" +let ocamlopt = "OCAMLOPT" <--? tool_prefix ^ "ocamlopt" + +let ocaml_conf_var varname = shell (ocamlc ^ " -config-var " ^ varname) + +(********************************************************************* +*** Try to automatically guess OS ***) + +(* Cygwin is for doing POSIX builds in Windows. + MinGW is for native Windows builds in a minimal POSIX environment. + MSVC is for native Windows builds without POSIX environment. *) + +let osarch = "OSARCH" <--? + match ocaml_conf_var "os_type" with + | "Win32" -> "Win32" (* Native Windows build *) + | "Cygwin" -> "Cygwin" (* POSIX build for Windows *) + | _ -> shell "uname" +(* Darwin is reported for macOS + SunOS is reported for Solaris, OpenSolaris and derivatives (illumos) *) + +let osarch_win32 = osarch = "Win32" +let osarch_cygwin = osarch = "Cygwin" +let osarch_macos = osarch = "Darwin" + +(********************************************************************* +*** Try to automatically guess UI style ***) + +let () = + if inputs.$("UISTYLE") <> "" then + error "UISTYLE is no longer used. See build instructions." + +let ocaml_libdir = "OCAMLLIBDIR" <--? ocaml_conf_var "standard_library" + +let ocamlfind = get_command "ocamlfind" + +let build_GUI = + let has_lablgtk3 = + match ocamlfind with + | Some cmd -> shell ~err_null:true (cmd ^ " query lablgtk3") |> not_empty + | None -> exists ocaml_libdir "lablgtk3" + in + if not has_lablgtk3 then begin + info "GUI library lablgtk not found. GTK GUI will not be built." + end; + has_lablgtk3 +let () = if build_GUI then outp "guimaybe: gui" + +let build_macGUI = + if osarch_macos then begin + (* If XCode is not installed, xcodebuild is just a placeholder telling + that XCode is not installed and any invocation of xcodebuild results + in a non-0 exit code. *) + if Sys.command "xcodebuild -version > /dev/null" = 0 then + true + else begin + info "Not building macOS native GUI because XCode is not installed."; + false + end + end else begin + info "Not on macOS. macOS native GUI will not be built."; + false + end +let () = if build_macGUI then outp "macuimaybe: macui" + +(********************************************************************* +*** Default parameters ***) + +(* Generate backtrace information for exceptions *) +let () = "CAMLFLAGS" <-+= "-g $(INCLFLAGS)" + +let () = + [ + "CAMLCFLAGS", "CFLAGS", "-ccopt"; + "CAMLCFLAGS", "CPPFLAGS", "-ccopt"; + "CAMLLDFLAGS", "LDFLAGS", "-cclib"; + "CLIBS", "LDLIBS", "-cclib"; + ] + |> List.iter (fun (varname, envname, arg) -> + if not_empty inputs.$(envname) then + varname <-+= arg ^ " \"" ^ inputs.$(envname) ^ "\"") + +(* The messy situation requiring the use of OUTPUT_SEL was fixed in OCaml 4.13. + All usages of OUTPUT_SEL should be removed when 4.13 becomes a requirement. *) +let () = + if osarch_win32 then begin + (* Native Win32 build *) + "EXEC_EXT" <-- ".exe"; + "OBJ_EXT" <-- ocaml_conf_var "ext_obj"; + if ocaml_conf_var "ccomp_type" = "msvc" then begin + "OUTPUT_SEL" <-- "-Fo"; + "CLIBS" <-+= "-cclib user32.lib -cclib \"-link win32rc/unison.res\""; + outp "buildexecutable: win32rc/unison.res"; + end else begin + "OUTPUT_SEL" <-- "-o"; + "CLIBS" <-+= "-cclib \"-link win32rc/unison.res.lib\""; + outp "buildexecutable: win32rc/unison.res.lib"; + end; + (* Make Windows GUI come up with no console window *) + if ($)"UI_WINOS" <> "hybrid" then begin + "CAMLLDFLAGS_GUI" <-- "-cclib \"-subsystem windows\""; + (* -subsystem is a flexlink arg; + respective gcc args: -mwindows or -Wl,--subsystem,windows + respective MSVC linker arg: /SUBSYSTEM:WINDOWS *) + end; + "CWD" <-- "."; + "WINCOBJS" <-+= "system/system_win_stubs" ^ ($)"OBJ_EXT" ^ " lwt/lwt_unix_stubs" ^ ($)"OBJ_EXT"; + "WINOBJS" <-- "system/system_win.cmo"; + "SYSTEM" <-- "win"; + "building_for" <-- "Building for Windows"; + end else begin + (* Unix build, or Cygwin POSIX (GNU C) build *) + "OBJ_EXT" <-- ".o"; + "OUTPUT_SEL" <-- "'-o '"; + "WINOBJS" <-- ""; + "SYSTEM" <-- "generic"; + (* This is not strictly necessary as Cygwin can do a generic Unix build + (and is actually meant to). *) + if osarch_cygwin then begin + "CWD" <-- "."; + "EXEC_EXT" <-- ".exe"; + "CLIBS" <-+= "-cclib win32rc/unison.res.lib"; + outp "buildexecutable: win32rc/unison.res.lib"; + "building_for" <-- "Building for Cygwin"; + end else begin + "CWD" <-- shell "pwd"; + "EXEC_EXT" <-- ""; + (* openpty is in the libutil library *) + if not osarch_macos && osarch <> "SunOS" then begin + "CLIBS" <-+= "-cclib -lutil" + end; + if osarch = "SunOS" then begin + (* ACL functions *) + "CLIBS" <-+= "-cclib -lsec" + end; + "building_for" <-- "Building for Unix"; + end + end + +(********************************************************************* +*** Compilation boilerplate ***) + +let native = + let native = + let nat = ($)"NATIVE" |> String.lowercase_ascii in + if nat <> "true" && nat <> "false" then + get_command ocamlopt <> None + else bool_of_string nat + in + "NATIVE" <-- string_of_bool native; + native + +let () = + if native then begin + (* Set up for native code compilation *) + "CAMLC" <-- ocamlopt; + "CAMLOBJS" <-- "$(OCAMLOBJS:.cmo=.cmx)"; + "CAMLOBJS_TUI" <-- "$(OCAMLOBJS_TUI:.cmo=.cmx)"; + "CAMLOBJS_GUI" <-- "$(OCAMLOBJS_GUI:.cmo=.cmx)"; + "CAMLOBJS_FSM" <-- "$(FSMOCAMLOBJS:.cmo=.cmx)"; + "CAMLOBJS_MAC" <-- "$(OCAMLOBJS_MAC:.cmo=.cmx)"; + "CAMLLIBS" <-- "$(OCAMLLIBS:.cma=.cmxa)"; + "CAMLLIBS_TUI" <-- "$(OCAMLLIBS_TUI:.cma=.cmxa)"; + "CAMLLIBS_GUI" <-- "$(OCAMLLIBS_GUI:.cma=.cmxa)"; + "CAMLLIBS_MAC" <-- "$(OCAMLLIBS_MAC:.cma=.cmxa)"; + "CAMLLIBS_FSM" <-- "$(FSMOCAMLLIBS:.cma=.cmxa)"; + end else begin + (* Set up for bytecode compilation *) + "CAMLC" <-- ocamlc; + (* -output-complete-exe is available since OCaml 4.10 + OCaml > 5.2.0 no longer supports detection of compiler options, + hence the hack of comparing the output to -version. *) + if String.trim (shell (ocamlc ^ " -output-complete-exe -version 2>&1")) = + String.trim (shell (ocamlc ^ " -version")) then begin + "CAMLLDFLAGS" <-+= "-output-complete-exe"; (* can safely strip the binary *) + end else begin + "CAMLLDFLAGS" <-+= "-custom"; + end; + "CAMLOBJS" <-- "$(OCAMLOBJS)"; + "CAMLOBJS_TUI" <-- "$(OCAMLOBJS_TUI)"; + "CAMLOBJS_GUI" <-- "$(OCAMLOBJS_GUI)"; + "CAMLOBJS_MAC" <-- "$(OCAMLOBJS_MAC)"; + "CAMLOBJS_FSM" <-- "$(FSMOCAMLOBJS)"; + "CAMLLIBS" <-- "$(OCAMLLIBS)"; + "CAMLLIBS_TUI" <-- "$(OCAMLLIBS_TUI)"; + "CAMLLIBS_GUI" <-- "$(OCAMLLIBS_GUI)"; + "CAMLLIBS_MAC" <-- "$(OCAMLLIBS_MAC)"; + "CAMLLIBS_FSM" <-- "$(FSMOCAMLLIBS)"; + end + +let () = "WINDRES" <-- + (if not_empty tool_prefix then tool_prefix else begin + let cc = Filename.basename (ocaml_conf_var "c_compiler") in + let rec extract_prefix pre l = + match l with + | [] -> "" (* could not detect the prefix *) + | x :: xs -> + let found_base = + String.length x > 1 && String.(sub x 0 2 |> lowercase_ascii) = "cl" || + String.length x > 2 && String.(sub x 0 3 |> lowercase_ascii) = "gcc" + in + if not found_base then extract_prefix (x :: pre) xs + else if pre = [] then "" else (String.concat "-" (List.rev pre)) ^ "-" + in + extract_prefix [] (String.split_on_char '-' cc) + end) ^ "windres" + +let () = + if is_empty inputs.$("_NMAKE_VER") then + "ALL__SRC" <-- "$^$>" (* First one is for GNU and POSIX make, the other for BSD make *) + else + "ALL__SRC" <-- "$(**)" (* NMAKE; enclose in brackets for safety if not run by NMAKE *) + +let () = "rule_sep" <-- if not_empty inputs.$("ASSUME_DMAKE") then ":=" else ":" + +(********************************************************************* +*** User Interface setup ***) + +let () = + if not build_GUI && (not_empty inputs.$("_NMAKE_VER") || osarch = "OpenBSD") then + "CAMLFLAGS_GUI" <--? "" |> ignore + else + "CAMLFLAGS_GUI" <-+= + match ocamlfind with + | Some cmd -> + (* The weird quoting is required for Windows, but harmless in sh *) + shell (cmd ^ " query -format \"-I \"\"%d\"\"\" lablgtk3") ^ " " ^ + shell (cmd ^ " query -format \"-I \"\"%d\"\"\" cairo2") + | None -> "-I +lablgtk3 -I +cairo2" + +let () = + if osarch_macos && is_empty inputs.$("XCODEFLAGS") then + (* Prevent Xcode from trying to build universal binaries by default *) + "XCODEFLAGS" <-- "-arch " ^ (shell "uname -m") + +(********************************************************************* +*** Filesystem monitoring ***) + +let fsmon_outp = ref [] + +let fsmonitor_dir = + match osarch with + | "Linux" -> "FSMDIR" <--? "fsmonitor/inotify" + | "SunOS" -> "FSMDIR" <--? "fsmonitor/solaris" + | "Win32" -> "FSMDIR" <--? "fsmonitor/windows" + | "FreeBSD" | "OpenBSD" | "NetBSD" | "DragonFly" -> + begin + let libinotify_lib = + let pkg_lib = shell "pkg-config --libs libinotify 2> /dev/null || printf ' -linotify'" in + if not_empty pkg_lib then "-cclib '" ^ pkg_lib ^ "'" else "" in + let libinotify_inc = + let pkg_flags = shell "pkg-config --cflags libinotify 2> /dev/null" in + if not_empty pkg_flags then "-ccopt '" ^ pkg_flags ^ "'" else "" in + if shell ("{ printf '' > inotifytest__.ml ;" ^ ocamlc ^ " " ^ + ($)"CAMLCFLAGS" ^ " " ^ ($)"CAMLLDFLAGS" ^ " " ^ libinotify_lib ^ + " -o inotifytest__ inotifytest__.ml > /dev/null 2>&1 && printf true ; } ;\ + rm -f inotifytest__.ml inotifytest__.cm[oix] \ + inotifytest__.o inotifytest__ > /dev/null 2>&1") = "true" then begin + if not_empty libinotify_inc then begin + (* OpenBSD make does not support local variables *) + let target = if osarch <> "OpenBSD" then "$(FSMCOBJS): " else "" in + fsmon_outp := + (target ^ "CAMLCFLAGS_FSM_X = " ^ libinotify_inc) :: !fsmon_outp + end; + if not_empty libinotify_lib then + fsmon_outp := ("CLIBS_FSM = " ^ libinotify_lib) :: !fsmon_outp; + "FSMDIR" <--? "fsmonitor/inotify" + end else + inputs.$("FSMDIR") + end + | _ -> inputs.$("FSMDIR") + +let build_fsmonitor = + if not_empty fsmonitor_dir then begin + outp ("include " ^ fsmonitor_dir ^ "/Makefile"); + List.iter outp !fsmon_outp; + true + end else begin + info "fsmonitor implementation is not available or not configured for this system. fsmonitor will not be built."; + false + end +let () = if build_fsmonitor then outp "fsmonitor: fsmonitorexecutable" + + +let () = "RM" <-- if is_empty inputs.$("_NMAKE_VER") then "rm -f" else "del /f" + + +(*** Output configuration ***) + +let () = + Env.iter (fun k v -> print_string k; print_string " = "; print_endline v) !vars + +let () = + List.iter print_endline (List.rev !output) + +let () = + info ""; + info (($)"building_for"); + info ("NATIVE = " ^ ($)"NATIVE"); + info "" + +end + + +module Tools = struct + +module Env = Map.Make(String) + +let ( .$() ) m n = + try Env.find n m with Not_found -> + try + Env.filter + (fun k _ -> String.(equal (uppercase_ascii n) (uppercase_ascii k))) m + |> Env.choose |> snd + with Not_found -> "" + +let ( .%() ) m n = + try Some (Env.find n m) with Not_found -> + try + Some (Env.filter + (fun k _ -> String.(equal (uppercase_ascii n) (uppercase_ascii k))) m + |> Env.choose |> snd) + with Not_found -> None + +let make_env arr = + let add_var map s = + let k, v = + try + let open String in + let v = index s '=' in + sub s 0 v, + try sub s (v + 1) (length s - v - 1) with Invalid_argument _ -> "" + with Not_found -> s, "" + in + Env.add k v map + in + Array.fold_left add_var Env.empty arr + +let env = + Unix.handle_unix_error Unix.environment () |> make_env + +let args = + (if Array.length Sys.argv < 2 then [||] else + Array.sub Sys.argv 2 (Array.length Sys.argv - 2)) + |> make_env + +let inputs = + let override _ a b = + match a, b with + | Some _, None -> a + | _, Some _ -> b + | None, None -> None + in + Env.merge override env args + +let vars = ref Env.empty + +let ( $ ) k = + match !vars.%(k) with Some s -> s | None -> inputs.$(k) + +let ( <-- ) k v = + vars := Env.add k v !vars + +let ( <-+= ) k v = + vars := Env.add k (($)k ^ " " ^ v) !vars + +let ( <--? ) k v = + match !vars.%(k) with + | Some s -> s + | None -> begin + match inputs.%(k) with + | Some s -> vars := Env.add k s !vars; s + | None -> vars := Env.add k v !vars; v + end + +let info msg = + prerr_endline msg + +let error msg = + prerr_string "Error: "; + prerr_endline msg; + exit 2 + +let shell ?(err_null = false) cmd = + let shell' open_proc close_proc = + let outp = open_proc () in + let buf = Buffer.create 512 in + let () = try Buffer.add_channel buf outp 16384 with | End_of_file -> () in + (* Trim the final newline *) + let trim_end = + if Buffer.length buf >= 2 then begin + if Buffer.(sub buf (length buf - 2) 2) = "\r\n" then 2 + else if Buffer.(sub buf (length buf - 1) 1) = "\n" then 1 + else 0 + end else if Buffer.length buf >= 1 && Buffer.(sub buf (length buf - 1) 1) = "\n" then 1 + else 0 + in + match close_proc () with + | _ -> Buffer.(sub buf 0 (length buf - trim_end)) + in + Unix.handle_unix_error (fun () -> + if err_null then + let (sh_out, sh_in, _) as sh_full = Unix.open_process_full cmd [||] in + let () = close_out sh_in in + shell' (fun () -> sh_out) (fun () -> Unix.close_process_full sh_full) + else + let sh_out = Unix.open_process_in cmd in + shell' (fun () -> sh_out) (fun () -> Unix.close_process_in sh_out) + ) () + +let exec cmd = + let cmd = String.concat " " cmd in + print_endline cmd; + match Sys.command cmd with + | 0 -> () + | e -> error (string_of_int e) + +let path = + env.$("PATH") + |> String.split_on_char (if Sys.win32 then ';' else ':') + +let pathext = + if Sys.win32 || Sys.cygwin then + env.$("PATHEXT") |> String.split_on_char ';' + else + [""] + +let search_in_path ?(path = path) name = + Filename.concat + (List.find + (fun dir -> List.exists + (fun ext -> Sys.file_exists (Filename.concat dir (name ^ ext))) + pathext) + path) + name + +let search_in_path_opt ?path name = + try Some (search_in_path ?path name) with + | Not_found -> None + +let get_command name = search_in_path_opt name + +let exists dir name = + Sys.file_exists (Filename.concat dir name) + +let rm = + let rm' n = + if String.length n > 0 && n.[0] <> '-' && n.[0] <> '/' && n.[0] <> '\\' + && not (String.contains n '*') then + try Sys.remove n with Sys_error _ -> () + in + Array.iter rm' + +let is_empty s = + String.length (String.trim s) = 0 + +let not_empty s = not (is_empty s) + +end + + +let target_local_vars () = + let open Tools in + let is_openbsd_make = + is_empty inputs.$("_NMAKE_VER") && (shell "uname" = "OpenBSD") + in + (* NMAKE and OpenBSD don't support target-specific local variables *) + if is_empty inputs.$("_NMAKE_VER") && not is_openbsd_make then begin + let is_old_gmake = + not_empty inputs.$("MAKE") && + let s = shell ~err_null:true (inputs.$("MAKE") ^ " --version") in + if String.length s >= 10 then String.sub s 0 10 = "GNU Make 3" else false + in + let rule_sep = if not is_old_gmake then " $(rule_sep) " else ": " in + [ + ["$(NAME_GUI)$(EXEC_EXT) $(CAMLOBJS_GUI)"; rule_sep; "CAMLFLAGS_GUI_X = $(CAMLFLAGS_GUI)"]; + ["$(NAME_FSM)$(EXEC_EXT) $(CAMLOBJS_FSM) $(FSMOCAMLOBJS:.cmo=.cmi)"; rule_sep; "CAMLFLAGS_FSM_X = $(CAMLFLAGS_FSM)"]; + ["$(NAME)-blob.o $(CAMLOBJS_MAC)"; rule_sep; "CAMLFLAGS_MAC_X = $(CAMLFLAGS_MAC)"]; + ] + |> List.iter (fun l -> String.concat "" l |> print_endline) + end else + print_string + "CAMLFLAGS_GUI_X = $(CAMLFLAGS_GUI)\n\ + CAMLFLAGS_FSM_X = $(CAMLFLAGS_FSM)" + + +let project_info () = + let open Tools in + {|let myName = "|} ^ ($)"NAME" ^ {|"|} |> print_endline; + {|let myVersion = "|} ^ ($)"VERSION" ^ {|"|} |> print_endline; + {|let myMajorVersion = "|} ^ ($)"MAJORVERSION" ^ {|"|} |> print_endline + + +let install () = + let open Tools in + if not_empty inputs.$("_NMAKE_VER") || Sys.file_exists "src/unison.exe" then begin + let cwd = Sys.getcwd () in + let map_sep = + if String.contains cwd '\\' then function '/' -> '\\' | c -> c + else fun c -> c + in + let files = + let check_file name l = + if Sys.file_exists name then + (String.map map_sep (Filename.concat cwd name)) :: l + else l + in + List.fold_right check_file + ["src/unison.exe"; "src/unison-gui.exe"; "src/unison-fsmonitor.exe"] + [] + in + if files = [] then + error "The application has not been built yet." + else + info ("\n\nYou can find the built application as the following files \ + that you can copy to your desired destination.\n " ^ + (String.concat "\n " files) ^ "\n"); + exit 0 + end; + + let install = "INSTALL" <--? "install" in + let install_program = "INSTALL_PROGRAM" <--? install in + let install_data = "INSTALL_DATA" <--? install ^ " -m 644" in + + let destdir = ($)"DESTDIR" in + let prefix = "PREFIX" <--? "/usr/local" in + let exec_prefix = "EXEC_PREFIX" <--? prefix in + let bindir = "BINDIR" <--? exec_prefix ^ "/bin" in + let datarootdir = "DATAROOTDIR" <--? prefix ^ "/share" in + let mandir = "MANDIR" <--? datarootdir ^ "/man" in + let man1dir = "MAN1DIR" <--? mandir ^ "/man1" in + let manext = "MANEXT" <--? ".1" in + + let install_if_exists dir name dest = + if exists dir name then exec + [install_program; Filename.concat dir name; Filename.concat dest name] + in + + print_endline ("DESTDIR = " ^ destdir); + print_endline ("PREFIX = " ^ prefix); + exec [install; "-d"; destdir ^ bindir]; + install_if_exists "src" "unison" (destdir ^ bindir); + install_if_exists "src" "unison-gui" (destdir ^ bindir); + install_if_exists "src" "unison-fsmonitor" (destdir ^ bindir); + if not (exists "src" "unison-fsmonitor") then begin + (* FIXME: fsmonitor.py is legacy and unmaintained. Drop? *) + install_if_exists "src" "fsmonitor.py" (destdir ^ bindir) + end; + if exists "man" "unison.1" then begin + exec [install; "-d"; destdir ^ man1dir]; + exec [install_data; "man/unison.1"; destdir ^ man1dir ^ "/unison" ^ manext] + end; + if exists "src/uimac/build/Default" "Unison.app" then begin + print_endline ("!!! The GUI for macOS has been built but will NOT be \ + installed automatically. You can find the built GUI package at " ^ + (Filename.concat (Sys.getcwd ()) "src/uimac/build/Default/Unison.app")) + end + + +let run cmd_and_args = + let map_sep = + if Sys.win32 then function '/' -> '\\' | c -> c else fun c -> c + in + match Array.to_list cmd_and_args with + | [] -> () + | cmd :: args -> Tools.exec (String.map map_sep cmd :: args) + + +let () = + if Array.length Sys.argv < 2 then + Tools.error "Missing sub-command" + else + match Sys.argv.(1) with + | "conf" -> let module Conf = Make(Tools) in () + | "conf2" -> target_local_vars () + | "projectinfo" -> project_info () + | "install" -> install () + | "run" -> run (Array.sub Sys.argv 2 (Array.length Sys.argv - 2)) + | "rm" -> Tools.rm (Array.sub Sys.argv 2 (Array.length Sys.argv - 2)) + | s -> Tools.error ("Invalid sub-commmand '" ^ s ^ "'") diff --git a/src/uimac/Makefile b/src/uimac/Makefile index f10ae1226..0777558e7 100644 --- a/src/uimac/Makefile +++ b/src/uimac/Makefile @@ -4,10 +4,6 @@ default: $(MAKE) -C .. macui .PHONY: default -ifeq ($(strip $(XCODEFLAGS)),) - XCODEFLAGS=-arch $(shell uname -m) ## Prevent Xcode from trying to build universal binaries by default -endif - # Note: The OCaml library names changed starting with OCaml 5.1.0 .PHONY: macexecutable macexecutable: From c70c3e7bd7631a1311badf92463e52827c5382e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B5ivo=20Leedj=C3=A4rv?= Date: Tue, 17 Dec 2024 11:10:22 +0100 Subject: [PATCH 2/4] Makefile: Embed manpage makefile in main makefile... ... and build it only on POSIX-y platforms (not used in Windows anyway). --- Makefile | 15 +++------------ man/Makefile | 22 ---------------------- src/Makefile | 2 +- src/Makefile.OCaml | 25 +++++++++++++++++++++++++ src/make_tools.ml | 3 ++- 5 files changed, 31 insertions(+), 36 deletions(-) delete mode 100644 man/Makefile diff --git a/Makefile b/Makefile index 74a282940..7e59d4bca 100644 --- a/Makefile +++ b/Makefile @@ -23,8 +23,8 @@ src: FRC FRC: ; # Not all make seem to work without FRC, even with .PHONY -.PHONY: tui gui macui fsmonitor depend -tui gui macui fsmonitor depend: +.PHONY: tui gui macui fsmonitor manpage depend +tui gui macui fsmonitor manpage depend: cd src && $(MAKE) $@ # It's a wart that docs need "unison" built (vs some docs utility). @@ -32,12 +32,6 @@ tui gui macui fsmonitor depend: docs: src manpage cd doc && $(MAKE) -# "src" is a prerequisite to prevent parallel build errors. -# manpage builds currently require a pre-built "unison" binary. -.PHONY: manpage -manpage: src - cd man && $(MAKE) - .PHONY: test test: ocaml src/make_tools.ml run ./src/unison -ui text -selftest @@ -48,13 +42,10 @@ test: # that do not have the -C option, work with different (also non-POSIX) # shells, and not rely on single shell per line execution. .PHONY: clean -clean: clean_doc clean_man clean_src +clean: clean_doc clean_src .PHONY: clean_doc clean_doc: cd doc && $(MAKE) clean -.PHONY: clean_man -clean_man: - cd man && $(MAKE) clean .PHONY: clean_src clean_src: cd src && $(MAKE) clean diff --git a/man/Makefile b/man/Makefile deleted file mode 100644 index 7975d513a..000000000 --- a/man/Makefile +++ /dev/null @@ -1,22 +0,0 @@ --include ../src/Makefile.ProjectInfo - -all: $(NAME).1 - -../src/$(NAME)$(EXEC_EXT): - $(MAKE) -C ../src tui - -$(NAME).1: $(NAME).1.in opt_short.tmp opt_full.tmp - sed -e '/@OPTIONS_SHORT@/r ./opt_short.tmp' \ - -e '/@OPTIONS_SHORT@/d' \ - -e '/@OPTIONS_FULL@/r ./opt_full.tmp' \ - -e '/@OPTIONS_FULL@/d' $(NAME).1.in > $(NAME).1 - -# Listing of preferences -opt_short.tmp: ../src/$(NAME)$(EXEC_EXT) - ../src/$(NAME)$(EXEC_EXT) -prefsman short > opt_short.tmp - -opt_full.tmp: ../src/$(NAME)$(EXEC_EXT) - ../src/$(NAME)$(EXEC_EXT) -prefsman full > opt_full.tmp - -clean: - $(RM) *.tmp $(NAME).1 diff --git a/src/Makefile b/src/Makefile index 734ef9854..a02a32c22 100644 --- a/src/Makefile +++ b/src/Makefile @@ -33,7 +33,7 @@ Makefile.cfg: _mk.cfg @ocaml make_tools.ml conf2 MAKE="$(MAKE)" > Makefile2.cfg _mk.cfg: ; -TARGETS = all tui gui macui fsmonitor clean depend dependgraph paths +TARGETS = all tui gui macui fsmonitor manpage clean depend dependgraph paths FILES = unison unison.exe .NOTPARALLEL: diff --git a/src/Makefile.OCaml b/src/Makefile.OCaml index 3f79547bb..d4d01b4fe 100644 --- a/src/Makefile.OCaml +++ b/src/Makefile.OCaml @@ -233,6 +233,31 @@ $(NAME)-blob.o: $(CAMLOBJS) $(CAMLOBJS_MAC) $(COBJS) $(LD) -r -keep_private_externs -o $@ u-b.o $(COBJS) $(RM) u-b.o +#################################################################### +### Documentation + +.PHONY: manpage +manpage: + +.PHONY: manpagefile +manpagefile: ../man/$(NAME).1 + +../man/$(NAME).1: ../man/$(NAME).1.in opt_short.tmp opt_full.tmp + sed -e '/@OPTIONS_SHORT@/r ./opt_short.tmp' \ + -e '/@OPTIONS_SHORT@/d' \ + -e '/@OPTIONS_FULL@/r ./opt_full.tmp' \ + -e '/@OPTIONS_FULL@/d' ../man/$(NAME).1.in > $@ + +# Listing of preferences +opt_short.tmp: $(NAME)$(EXEC_EXT) + ./$(NAME)$(EXEC_EXT) -prefsman short > $@ + +opt_full.tmp: $(NAME)$(EXEC_EXT) + ./$(NAME)$(EXEC_EXT) -prefsman full > $@ + +clean:: + cd ../man && $(RM) *.tmp $(NAME).1 + #################################################################### ### Misc diff --git a/src/make_tools.ml b/src/make_tools.ml index b6ab3dba5..e34e0bd13 100644 --- a/src/make_tools.ml +++ b/src/make_tools.ml @@ -174,7 +174,8 @@ let () = "CLIBS" <-+= "-cclib -lsec" end; "building_for" <-- "Building for Unix"; - end + end; + outp "manpage: manpagefile"; end (********************************************************************* From b14ab18f4790e337bf6e254e76231fe46889efeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B5ivo=20Leedj=C3=A4rv?= Date: Tue, 17 Dec 2024 11:17:01 +0100 Subject: [PATCH 3/4] Makefile: Trigger docs from main makefile... ... and build docs only on POSIX-y platforms (can't easily build in Windows). --- Makefile | 21 ++------------------- src/Makefile | 2 +- src/Makefile.OCaml | 7 +++++++ src/make_tools.ml | 2 ++ 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 7e59d4bca..a886d3001 100644 --- a/Makefile +++ b/Makefile @@ -23,33 +23,16 @@ src: FRC FRC: ; # Not all make seem to work without FRC, even with .PHONY -.PHONY: tui gui macui fsmonitor manpage depend -tui gui macui fsmonitor manpage depend: +.PHONY: tui gui macui fsmonitor manpage docs clean depend +tui gui macui fsmonitor manpage docs clean depend: cd src && $(MAKE) $@ -# It's a wart that docs need "unison" built (vs some docs utility). -.PHONY: docs -docs: src manpage - cd doc && $(MAKE) - .PHONY: test test: ocaml src/make_tools.ml run ./src/unison -ui text -selftest # Note: unison binary is not built automatically for the test target, # so as to avoid building it with unwanted configuration. -# This construct is here to remain compatible with make implementations -# that do not have the -C option, work with different (also non-POSIX) -# shells, and not rely on single shell per line execution. -.PHONY: clean -clean: clean_doc clean_src -.PHONY: clean_doc -clean_doc: - cd doc && $(MAKE) clean -.PHONY: clean_src -clean_src: - cd src && $(MAKE) clean - prefix = /usr/local .PHONY: install diff --git a/src/Makefile b/src/Makefile index a02a32c22..3680286ad 100644 --- a/src/Makefile +++ b/src/Makefile @@ -33,7 +33,7 @@ Makefile.cfg: _mk.cfg @ocaml make_tools.ml conf2 MAKE="$(MAKE)" > Makefile2.cfg _mk.cfg: ; -TARGETS = all tui gui macui fsmonitor manpage clean depend dependgraph paths +TARGETS = all tui gui macui fsmonitor manpage docs clean depend dependgraph paths FILES = unison unison.exe .NOTPARALLEL: diff --git a/src/Makefile.OCaml b/src/Makefile.OCaml index d4d01b4fe..f959047e9 100644 --- a/src/Makefile.OCaml +++ b/src/Makefile.OCaml @@ -258,6 +258,13 @@ opt_full.tmp: $(NAME)$(EXEC_EXT) clean:: cd ../man && $(RM) *.tmp $(NAME).1 +.PHONY: docs +docs: + +.PHONY: docfiles +docfiles: manpagefile + cd ../doc && $(MAKE) all + #################################################################### ### Misc diff --git a/src/make_tools.ml b/src/make_tools.ml index e34e0bd13..9407a0681 100644 --- a/src/make_tools.ml +++ b/src/make_tools.ml @@ -176,6 +176,8 @@ let () = "building_for" <-- "Building for Unix"; end; outp "manpage: manpagefile"; + outp "docs: docfiles"; + outp "clean::\n\tcd ../doc && $(MAKE) clean"; end (********************************************************************* From 0b55693f968be47c6f4b3e97d05de03c8154b791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B5ivo=20Leedj=C3=A4rv?= Date: Tue, 17 Dec 2024 11:20:10 +0100 Subject: [PATCH 4/4] Makefile: Make docs makefile portable This patch makes the docs makefile portable with GNU Make, BSD make and Solaris make by: - removing all pattern rules - removing all conditionals from makefile (ok in shell) - removing all obscure macro functions --- doc/Makefile | 104 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 40 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index f406966e8..d606f7c3c 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,25 +1,30 @@ # Manual +# IMPORTANT! +# +# This file is portable and compatible with GNU Make, BSD make and +# Solaris (d)make. Do not make any changes in this file unless you +# are certain that the changes do not break this portability. + + +# a shorthand just for brevity +m = unison-manual + .PHONY: all -all: unison-manual.pdf unison-manual.html unison-manual.txt ../src/strings.ml +all: $(m).pdf $(m).html $(m).txt ../src/strings.ml DRAFT = false --include ../src/Makefile.ProjectInfo +include ../src/Makefile.ProjectInfo SOURCES = unison-manual.tex \ local.tex short.tex \ unisonversion.tex prefs.tmp prefsdocs.tmp -FORMATS = html pdf txt dvi ps info -FORMATS_AUX = dtxt aux htoc toc -$(addprefix unison-manual., $(FORMATS) $(FORMATS_AUX)): $(SOURCES) +$(m).pdf $(m).html $(m).txt $(m).ps: $(SOURCES) +$(m).dtxt $(m).aux $(m).htoc $(m).toc: $(SOURCES) -unison-manual-directives.tex unison-manual-text-directives.tex: - -ifneq ($(strip $(shell hevea -version)),) - HEVEA=true -endif +HEVEA_FOUND = test -n "$$(command -v hevea > /dev/null 2>&1 && echo true)" unisonversion.tex: ../src/Makefile.ProjectInfo echo '\def\unisonversion{$(VERSION)}' > $@ @@ -28,54 +33,73 @@ unisonversion.tex: ../src/Makefile.ProjectInfo # jobname (or, output name for hevea) so that files for non-text versions are # not overwritten. Here, suffix -text is used. -%-text-directives.tex: +$(m)-text-directives.tex: printf '$(TEXDIRECTIVES)\\textversiontrue\\draft$(DRAFT)' > $@ -%.dtxt: %.tex %-text-directives.tex -ifeq ($(HEVEA),true) - hevea -o $*-text.html $< - (TERM=vt100; export TERM; lynx -display_charset=utf8 -dump $*-text.html > $@) -endif +.SUFFIXES: +.SUFFIXES: .tex .pdf .ps .html .dtxt .txt + +$(m).dtxt: $(m)-text-directives.tex -%.txt: %.dtxt -ifeq ($(HEVEA),true) - sed -e "/^----SNIP----/,+2 d" -e "/^Junk/,$$ d" $< > $@ -endif +.tex.dtxt: + if $(HEVEA_FOUND) ; then \ + hevea -o $*-text.html $< ; \ + (TERM=vt100; export TERM; lynx -display_charset=utf8 -dump $*-text.html > $@) \ + ; fi -../src/strings.ml: unison-manual.dtxt docs.ml -ifeq ($(HEVEA),true) - ocaml docs.ml < $< > $@ -endif +.dtxt.txt: + if $(HEVEA_FOUND) ; then \ + sed -e "/^----SNIP----/,+2 d" -e "/^Junk/,$$ d" $< > $@ \ + ; fi -%-directives.tex: +../src/strings.ml: $(m).dtxt docs.ml + if $(HEVEA_FOUND) ; then \ + ocaml docs.ml < $(m).dtxt > $@ \ + ; fi + +$(m)-directives.tex: printf '$(TEXDIRECTIVES)\\textversionfalse\\draft$(DRAFT)' > $@ # (pdf)latex must be run multiple times to generate toc and correct references -%.aux %.htoc: %.tex %-directives.tex - pdflatex -draftmode $< - pdflatex -draftmode $< - -%.pdf: %.tex %-directives.tex %.aux +$(m).aux $(m).htoc &: $(m).tex $(m)-directives.tex + pdflatex -draftmode $(m).tex && \ + pdflatex -draftmode $(m).tex +# &: is the GNU Make group target separator. Other make implementations do not +# support it but accept it nevertheless because it's treated as a target named & +# Without group targets, the same outcome is achieved by the following: +.ORDER: $(m).aux $(m).htoc # BSD make +.NO_PARALLEL$(.MAKE.PID): $(m).aux $(m).htoc # Solaris make + # BSD make interprets .NO_PARALLEL as the entire makefile not parallel. + # .MAKE.PID (which is only defined by BSD make) is in there just to prevent + # BSD make from interpreting this rule + +$(m).pdf: $(m).tex $(m)-directives.tex $(m).aux + +.tex.pdf: pdflatex $< -%.ps: %.pdf +.pdf.ps: pdf2ps $< -%.html: %.tex %-directives.tex %.htoc -ifeq ($(HEVEA),true) - hevea $< -endif +$(m).html: $(m).tex $(m)-directives.tex $(m).htoc + +.tex.html: + if $(HEVEA_FOUND) ; then \ + hevea $< \ + ; fi # Listing of preferences -prefs.tmp: ../src/$(NAME)$(EXEC_EXT) - -../src/$(NAME)$(EXEC_EXT) -help > prefs.tmp -prefsdocs.tmp: ../src/$(NAME)$(EXEC_EXT) - -../src/$(NAME)$(EXEC_EXT) -prefsdocs 2> prefsdocs.tmp +prefs.tmp: ../src/$(NAME) + -../src/$(NAME) -help > prefs.tmp +prefsdocs.tmp: ../src/$(NAME) + ../src/$(NAME) -prefsdocs 2> prefsdocs.tmp -../src/$(NAME)$(EXEC_EXT): +../src/$(NAME): $(MAKE) -C ../src tui +RM = rm -f + .PHONY: clean clean: $(RM) -r \