From a128b40f03c1c676a68fce8e429b26e62e57d733 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Wed, 7 May 2014 23:47:20 +0200 Subject: [PATCH] Tool for preparing a single 3rd-party port Issue #1082 --- .gitignore | 2 +- tool/ports/mk/install.mk | 273 +++++++++++++++++++++++++++++++++++++++ tool/ports/prepare_port | 133 +++++++++++++++++++ 3 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 tool/ports/mk/install.mk create mode 100755 tool/ports/prepare_port diff --git a/.gitignore b/.gitignore index a468b82b1..9f346504d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ *.rej /build - +/contrib /repos/base-codezero/contrib /repos/base-fiasco/contrib /repos/base-fiasco/download diff --git a/tool/ports/mk/install.mk b/tool/ports/mk/install.mk new file mode 100644 index 000000000..161fa0b1b --- /dev/null +++ b/tool/ports/mk/install.mk @@ -0,0 +1,273 @@ +# +# \brief Download and patch port of 3rd-party source code +# \author Norman Feske +# \date 2014-05-07 +# +# This makefile must be invoked from the port directory. +# +# Arguments: +# +# PORT - port description file +# +# This makefile includes the port description file. The namespace for variables +# is organized as follows: Variables and functions that are private to the +# prepare_port tool are prefixed with '_'. Port description files must not +# declare any variable with a leading '_'. Variables that interface the +# prepare_port tool with the port description file are written uppercase. Local +# helper variables in the port description files should be written lowercase. +# + +# XXX remove this line when the tool has stabilized +STRICT_HASH ?= no + +default: + +# repository that contains the port description, used to look up patch files +REP_DIR := $(realpath $(dir $(PORT))/..) + +# +# Check presence of argument $1. Back out with error message $2 if not defined. +# +_assert = $(if $(strip $1),$1,$(info Error: $(strip $2))$(error )) + +# +# Helper function that returns $1 if defined, otherwise it returns $2 +# +_prefer = $(if $1,$1,$2) + +# +# Include definitions provided by the port description file +# +include $(PORT) + +# +# Common environment +# +SHELL := bash +VERBOSE ?= @ +ECHO := echo -e +HASHSUM := sha1sum + +BRIGHT_COL ?= \033[01;33m +DARK_COL ?= \033[00;33m +DEFAULT_COL ?= \033[0m + +MSG_PREFIX := $(ECHO) "$(DARK_COL)$(notdir $(PORT:.port=)) $(DEFAULT_COL)" +MSG_DOWNLOAD := $(MSG_PREFIX)"download " +MSG_APPLY := $(MSG_PREFIX)"apply " +MSG_UPDATE := $(MSG_PREFIX)"update " +MSG_INSTALL := $(MSG_PREFIX)"install " +MSG_GENERATE := $(MSG_PREFIX)"generate " +MSG_EXTRACT := $(MSG_PREFIX)"extract " + +# +# Assertion for the presence of a LICENSE and VERSION declarations in the port +# description +# +ifeq ($(LICENSE),) +default: license_undefined +license_undefined: + @$(ECHO) "Error: License undefined"; false +endif + +ifeq ($(VERSION),) +default: version_undefined +version_undefined: + @$(ECHO) "Error: Version undefined"; false +endif + + +_PATCHES_IN_REP_DIR := $(foreach P,$(PATCHES),$(wildcard $(REP_DIR)/$(P))) + +_DST_HASH_FILE := $(notdir $(PORT:.port=)).hash + + +# +# Default rule that triggers the actual preparation steps +# +default: $(DOWNLOADS) _patch $(_DST_HASH_FILE) _dirs + +_dirs: $(DOWNLOADS) + + +## +## Generate the HASH file +## + +# The hash depends on the content of the port description file and the +# patches originating from REP_DIR. Patches that are downloaded are already +# captured by the hash sums of the downloaded files, which are declared in the +# port description file. +# +# During development when the files of MAKEFILE_LIST often change, the +# keeping all port hashes up to date is an inconvenience. By setting +# STRICT_HASH to 'no', the MAKEFILE_LIST can be exluded. +# + +HASH_INPUT += $(_PATCHES_IN_REP_DIR) $(PORT) +ifneq ($(STRICT_HASH),no) +HASH_INPUT += $(MAKEFILE_LIST) +endif + +$(_DST_HASH_FILE): $(HASH_INPUT) $(MAKEFILE_LIST) + @$(MSG_GENERATE)$(notdir $@) + $(VERBOSE)cat $(HASH_INPUT) | $(HASHSUM) | sed "s/ .*//" > $@ + +_check_hash: $(_DST_HASH_FILE) +ifneq ($(CHECK_HASH),no) + $(VERBOSE)diff $< $(HASH_FILE) > /dev/null ||\ + ($(ECHO) "Error: $(_REL_HASH_FILE) is out of date, expected" `cat $<` ""; false) +endif + + +## +## Apply patches +## + +# default arguments for the patch command +PATCH_OPT ?= -p0 + +# +# Helper function to obtain options for applying a patch +# +_patch_opt = $(call _prefer,$(PATCH_OPT($1)),$(PATCH_OPT)) + +# +# Helper function to look up the input file for a patch +# +_patch_input = $(wildcard $1 $(REP_DIR)/$1) + +# +# Rules for applying patches +# +# The 'patch' rule is a phony rule that is used as default rule. It triggers +# all other steps such as downloading and hash-sum checks. For each patch, there +# dependency to a phony rule. +# +_PATCH_TARGETS := $(addprefix phony/patches/,$(PATCHES)) + +_patch: $(_PATCH_TARGETS) + +$(_PATCH_TARGETS): $(DOWNLOADS) + +phony/patches/%: + @$(MSG_APPLY)$* + $(VERBOSE)test -f $(firstword $(call _patch_input,$*) fail) ||\ + ($(ECHO) "Error: Could not find patch $*"; false) + $(VERBOSE)for i in $(call _patch_input,$*); do\ + patch -s $(call _patch_opt,$*) < $$i; done + + +## +## Assemble custom directory structures within the port directory +## + +_DIR_TARGETS := $(addprefix phony/dirs/,$(DIRS)) + +_dirs: $(_DIR_TARGETS) + +$(_DIR_TARGETS): _patch + +_dir_content = $(call _assert,$(DIR_CONTENT($1)), \ + Missing declaration of DIR_CONTENT($1)) + +phony/dirs/%: + @$(MSG_INSTALL)$* + $(VERBOSE)mkdir -p $* + $(VERBOSE)cp -Lrf $(call _dir_content,$*) $* + + +## +## Obtain source codes from a Git repository +## + +_git_dir = $(call _assert,$(DIR($1)),Missing declaration of DIR($*)) + +%.git: + $(VERBOSE)test -n "$(REV($*))" ||\ + ($(ECHO) "Error: Undefined revision for $*"; false); + $(VERBOSE)test -n "$(URL($*))" ||\ + ($(ECHO) "Error: Undefined URL for $*"; false); + $(VERBOSE)dir=$(call _git_dir,$*);\ + test -d $$dir || $(MSG_DOWNLOAD)$(URL($*)); \ + test -d $$dir || git clone $(URL($*)) $$dir; \ + $(MSG_UPDATE)$$dir; \ + cd $$dir && git fetch && git reset -q --hard HEAD && git checkout -q $(REV($*)) + + +## +## Obtain source codes from Subversion repository +## + +_svn_dir = $(call _assert,$(DIR($1)),Missing declaration of DIR($*)) + +%.svn: + $(VERBOSE)test -n "$(REV($*))" ||\ + ($(ECHO) "Error: Undefined revision for $*"; false); + $(VERBOSE)test -n "$(URL($*))" ||\ + ($(ECHO) "Error: Undefined URL for $*"; false); + $(VERBOSE)dir=$(call _svn_dir,$*);\ + rm -rf $$dir; \ + $(MSG_DOWNLOAD)$(URL($*)); \ + svn export -q $(URL($*))@$(REV($*)) $$dir; + + +## +## Download a plain file +## + +_file_name = $(call _prefer,$(NAME($1)),$(notdir $(URL($1)))) + +%.file: + $(VERBOSE)test -n "$(URL($*))" ||\ + ($(ECHO) "Error: Undefined URL for $(call _file_name,$*)"; false); + $(VERBOSE)name=$(call _file_name,$*);\ + (test -f $$name || $(MSG_DOWNLOAD)$(URL($*))); \ + (test -f $$name || wget --quiet $(URL($*)) -O $$name); + $(VERBOSE)\ + ($(ECHO) "$(SHA($*)) $(call _file_name,$*)" |\ + $(HASHSUM) -c > /dev/null 2> /dev/null) || \ + ($(ECHO) Error: Hash sum check for $* failed; false) + + +## +## Download and unpack an archive +## + +_archive_name = $(call _prefer,$(NAME($1)),$(notdir $(URL($1)))) +_archive_dir = $(call _assert,$(DIR($1)),Missing definition of DIR($*) in $(PORT)) + +_tar_opt = $(call _prefer,$(TAR_OPT($1)),--strip-components=1) + +# +# Archive extraction functions for various archive types +# +_extract_function(tgz) = tar xfz $(ARCHIVE) -C $(DIR) $(call _tar_opt,$1) +_extract_function(tar.gz) = tar xfz $(ARCHIVE) -C $(DIR) $(call _tar_opt,$1) +_extract_function(tar.xz) = tar xfJ $(ARCHIVE) -C $(DIR) $(call _tar_opt,$1) +_extract_function(tar.bz2) = tar xfj $(ARCHIVE) -C $(DIR) $(call _tar_opt,$1) +_extract_function(zip) = unzip -o -q -d $(DIR) $(ARCHIVE) + +_ARCHIVE_EXTS := tar.gz tar.xz tgz tar.bz2 zip + +# +# Function that returns the matching extraction function for a given archive +# +# Because this function refers to the 'ARCHIVE' variable, it is only supposed +# to work from the scope of the %.archive rule. +# +_extract_function = $(call _assert,\ + $(foreach E,$(_ARCHIVE_EXTS),\ + $(if $(filter %.$E,$(ARCHIVE)),\ + $(_extract_function($E)),)),\ + Don't know how to extract $(ARCHIVE)) + +%.archive: ARCHIVE = $(call _archive_name,$*) +%.archive: DIR = $(call _archive_dir,$*) + +%.archive: %.file + @$(MSG_EXTRACT)$(ARCHIVE) + $(VERBOSE)\ + mkdir -p $(DIR);\ + $(call _extract_function,$*) + diff --git a/tool/ports/prepare_port b/tool/ports/prepare_port new file mode 100755 index 000000000..20a24e6d6 --- /dev/null +++ b/tool/ports/prepare_port @@ -0,0 +1,133 @@ +#!/usr/bin/make -f + +# +# \brief Tool for downloading and patching 3rd-party source code +# \author Norman Feske +# \date 2014-05-07 +# + +VERBOSE ?= @ +ECHO := echo -e + +# +# Always execute the $(TARGET) rule regardless of the supplied command-line +# argument +# +TARGET := $(MAKECMDGOALS) default +$(MAKECMDGOALS) default: + +# the single argument is the name of the port to prepare +ARGUMENT := $(MAKECMDGOALS) +PORT_NAME := $(firstword $(ARGUMENT)) + +# +# Determine Genode base directory based on the known location of the +# 'create_builddir' tool within the Genode source tree +# +GENODE_DIR ?= $(realpath $(dir $(MAKEFILE_LIST))/../..) + +# compound directory where all 3rd-party source codes are installed +CONTRIB_DIR ?= $(GENODE_DIR)/contrib + +# list of all repositories located at '/repos/' +REPOSITORIES ?= $(shell find $(GENODE_DIR)/repos -follow -mindepth 1 -maxdepth 1 -type d) + +# list of all repositories that contain ports of 3rd-party source code +_REP_PORTS_DIRS := $(wildcard $(addsuffix /ports,$(REPOSITORIES))) + +# list of all available ports +_PORTS := $(foreach DIR,$(_REP_PORTS_DIRS),$(wildcard $(DIR)/*.port)) + +# port file to use +PORT := $(filter %/$(PORT_NAME).port,$(_PORTS)) + +# repository that contains the port description, used to look up patch files +REP_DIR := $(realpath $(dir $(PORT))/..) + +# read hash that uniquely identifies the version to install +HASH_FILE := $(wildcard $(PORT:.port=.hash)) +HASH := $(if $(HASH_FILE),$(shell cat $(HASH_FILE)),) + +# path to hash file relative to '/repos', used for error messages +_REL_HASH_FILE := $(notdir $(REP_DIR))/ports/$(notdir $(PORT)) + +# directory where to install the port +PORT_DIR := $(CONTRIB_DIR)/$(PORT_NAME)-$(HASH) + +# path to hash file generated during installation +PORT_HASH_FILE := $(PORT_DIR)/$(PORT_NAME).hash + +# +# Check validity of user input +# +ifeq ($(ARGUMENT),) +$(TARGET): missing_argument +missing_argument: usage + @$(ECHO) "Error: Missing port name as argument"; false +endif + +ifneq ($(words $(ARGUMENT)),1) +$(TARGET): too_many_arguments +too_many_arguments: usage + @$(ECHO) "Error: Too many arguments specified, expecting one argument"; false +endif + +ifeq ($(PORT),) +$(TARGET): nonexisting_port +nonexisting_port: + @$(ECHO) "Error: Port $(PORT_NAME) does not exist"; false +endif + +ifeq ($(HASH),) +$(TARGET): nonexisting_hash +nonexisting_hash: + @$(ECHO) "Error: Port $(PORT_NAME) lacks a valid hash"; false +endif + +usage: + @$(ECHO) + @$(ECHO) "--- download and patch 3rd-party source code ---" + @$(ECHO) "usage: prepare_port [CHECK_HASH=no]" + @$(ECHO) + +# +# Default rule that triggers the actual preparation steps +# +$(TARGET): _check_integrity + +_check_integrity : _install_in_port_dir +ifneq ($(CHECK_HASH),no) + $(VERBOSE)diff $(PORT_HASH_FILE) $(HASH_FILE) > /dev/null ||\ + ($(ECHO) "Error: $(_REL_HASH_FILE) is out of date, expected" `cat $(PORT_HASH_FILE)` ""; false) +endif + +# +# During the preparatio steps, the port directory is renamed. We use the suffix +# ".incomplete" to mark this transient state of the port directory. +# +_install_in_port_dir: $(PORT_DIR) + @#\ + # if the transient directory already exists, reuse it as it may contain\ + # finished steps such as downloads. By reusing it, we avoid downloading\ + # the same files again and again while working on a port. However, in this\ + # case, we already have created an empty port directory via the $(PORT_DIR)\ + # rule below. To avoid having both the port directory and the transient\ + # port directory present, remove the just-created port directory.\ + # + $(VERBOSE)(test -d $(PORT_DIR).incomplete && rmdir $(PORT_DIR)) || true + @#\ + # If no transient directory exists, rename the port directory accordingly.\ + # + $(VERBOSE)test -d $(PORT_DIR).incomplete || mv --force $(PORT_DIR) $(PORT_DIR).incomplete + $(VERBOSE)$(MAKE) --no-print-directory \ + -f $(GENODE_DIR)/tool/ports/mk/install.mk \ + -C $(PORT_DIR).incomplete \ + PORT=$(PORT) VERBOSE=$(VERBOSE) + @#\ + # The preparation finished successfully. So we can rename the transient\ + # directory to the real one.\ + # + $(VERBOSE)mv $(PORT_DIR).incomplete $(PORT_DIR) + +$(PORT_DIR): + $(VERBOSE)mkdir -p $@