You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucy.apache.org by nw...@apache.org on 2017/02/09 15:55:47 UTC

lucy git commit: Add "install" target to C Makefile

Repository: lucy
Updated Branches:
  refs/heads/master 169c2eccd -> d6fcd5fb4


Add "install" target to C Makefile

Fixes LUCY-319.


Project: http://git-wip-us.apache.org/repos/asf/lucy/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy/commit/d6fcd5fb
Tree: http://git-wip-us.apache.org/repos/asf/lucy/tree/d6fcd5fb
Diff: http://git-wip-us.apache.org/repos/asf/lucy/diff/d6fcd5fb

Branch: refs/heads/master
Commit: d6fcd5fb475f6cbeedb72ccd5bcbfa5339b241fa
Parents: 169c2ec
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Wed Feb 8 20:01:08 2017 +0100
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Thu Feb 9 16:43:58 2017 +0100

----------------------------------------------------------------------
 c/INSTALL                    |  76 ----
 c/INSTALL.md                 |  76 ++++
 c/install.sh                 | 121 ------
 common/charmonizer.c         | 755 +++++++++++++++++++++++++++-----------
 common/charmonizer.main      |  49 ++-
 devel/bin/appveyor-build.bat |  14 +-
 devel/bin/test_all.sh        |  10 +-
 devel/bin/travis-test.sh     |  10 +-
 devel/bin/update_version     |   8 -
 9 files changed, 681 insertions(+), 438 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy/blob/d6fcd5fb/c/INSTALL
----------------------------------------------------------------------
diff --git a/c/INSTALL b/c/INSTALL
deleted file mode 100644
index 01dd841..0000000
--- a/c/INSTALL
+++ /dev/null
@@ -1,76 +0,0 @@
-Build instructions for the Apache Lucy C library
-================================================
-
-Clownfish dependency
---------------------
-
-IMPORTANT: The Apache Lucy C library depends on the Clownfish object system
-which must be built first. Please read this section, or your build will
-probably FAIL.
-
-Lucy needs the following Clownfish components:
-
-    * The command-line Clownfish compiler 'cfc'.
-    * The Clownfish header files of the Clownfish runtime.
-    * The 'libcfish' shared library.
-
-If you installed Clownfish to /usr/local or /usr on UNIX, no further action
-should be required. If you installed Clownfish to an alternate location,
-make sure to use the --clownfish-prefix configure option:
-
-    $ ./configure --clownfish-prefix=clownfish-install-dir
-
-It's also possible to build Lucy with an uninstalled Clownfish build from
-the Clownfish source directory. To setup the required environment variables,
-source the file devel/bin/setup_env.sh from the Clownfish source tree:
-
-    $ source path_to_clownfish/devel/bin/setup_env.sh
-
-Or, if you're on the Windows shell:
-
-    $ path_to_clownfish/devel/bin/setup_env.bat
-
-Building under UNIX and derivatives or Cygwin
----------------------------------------------
-
-    $ ./configure
-    $ make
-    $ make test
-
-Building under Windows
-----------------------
-
-You need MSVC or gcc as C compiler and nmake or mingw32-make as make utility.
-
-When using cmd.exe configure with:
-
-    $ configure.bat
-
-When using the MSYS shell configure with:
-
-    $ ./configure
-
-When building with nmake run:
-
-    $ nmake
-    $ nmake test
-
-When building with mingw32-make run:
-
-    $ mingw32-make
-    $ mingw32-make test
-
-Configuration
--------------
-
-    ./configure [ options ] [ -- cflags ]
-
-Options include
-
-    --enable-coverage
-        Enable code coverage. Create HTML pages with coverage data using
-        lcov by running "make coverage".
-    --clownfish-prefix=path
-        Specify an alternative location for Clownfish if it isn't installed
-        in a system directory.
-

http://git-wip-us.apache.org/repos/asf/lucy/blob/d6fcd5fb/c/INSTALL.md
----------------------------------------------------------------------
diff --git a/c/INSTALL.md b/c/INSTALL.md
new file mode 100644
index 0000000..a3d7b64
--- /dev/null
+++ b/c/INSTALL.md
@@ -0,0 +1,76 @@
+Build instructions for the Apache Lucy C library
+================================================
+
+Clownfish dependency
+--------------------
+
+**IMPORTANT:** The Apache Lucy C library depends on the Clownfish object system
+which must be built first. Please read this section, or your build will
+probably **FAIL**.
+
+Lucy needs the following Clownfish components:
+
+* The command-line Clownfish compiler 'cfc'.
+* The Clownfish header files of the Clownfish runtime.
+* The 'libcfish' shared library.
+
+If you installed Clownfish to /usr/local or /usr on UNIX, no further action
+should be required. If you installed Clownfish to an alternate location,
+make sure to use the `--clownfish-prefix` configure option:
+
+    ./configure --clownfish-prefix=[clownfish-install-dir]
+
+It's also possible to build Lucy with an uninstalled Clownfish build from
+the Clownfish source directory. To setup the required environment variables,
+source the file `devel/bin/setup_env.sh` from the Clownfish source tree:
+
+    source [path_to_clownfish]/devel/bin/setup_env.sh
+
+Or, if you're on the Windows shell:
+
+    [path_to_clownfish]/devel/bin/setup_env.bat
+
+Building under UNIX and derivatives or Cygwin
+---------------------------------------------
+
+    ./configure --prefix [install-prefix]
+    make
+    make test
+    make install
+
+Building under Windows
+----------------------
+
+You need MSVC or gcc as C compiler and nmake, mingw32-make, or standard
+GNU make as make utility. When building under cmd.exe, configure with:
+
+    configure.bat --prefix [install-prefix]
+
+Configuration
+-------------
+
+    [environment] ./configure [options] [-- [cflags]]
+
+### Options
+
+    --enable-coverage
+
+Enable code coverage. Create HTML pages with coverage data using
+lcov by running "make coverage".
+
+    --clownfish-prefix=[path]
+
+Specify an alternative location for Clownfish if it isn't installed
+in a system directory.
+
+### Environment variables
+
+    CC
+
+The C compiler.
+
+    TARGET_CC
+
+The target compiler when cross-compiling. `CC` can be set in addition
+to specify the host compiler.
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/d6fcd5fb/c/install.sh
----------------------------------------------------------------------
diff --git a/c/install.sh b/c/install.sh
deleted file mode 100755
index 9e73ee5..0000000
--- a/c/install.sh
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/bin/sh
-
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -e
-
-version=0.6.0
-major_version=0.6
-
-usage()
-{
-    echo "Usage: install.sh --prefix path"
-}
-
-while [ -n "${1+set}" ]; do
-    case "$1" in
-        -h|--help|-\?)
-            usage
-            exit
-            ;;
-        --prefix)
-            if [ -z "${2+set}" ]; then
-                echo "--prefix requires an argument."
-                exit 1
-            fi
-            prefix=$2
-            shift 2
-            ;;
-        *)
-            echo "Invalid option: '$1'" 1>&2
-            usage
-            exit 1
-            ;;
-    esac
-done
-
-if [ -z "$prefix" ]; then
-    echo "No prefix specified."
-    usage
-    exit 1
-fi
-
-if ! mkdir -p "$prefix"; then
-    echo "Can't create directory: $prefix"
-    exit 1
-fi
-
-prefix=`cd "$prefix" && pwd`
-
-# Install libraries.
-case `uname` in
-    Darwin*)
-        lib_file=liblucy.$version.dylib
-        if [ ! -f $lib_file ]; then
-            echo "$lib_file not found. Did you run make?"
-            exit 1
-        fi
-        mkdir -p "$prefix/lib"
-        cp $lib_file "$prefix/lib"
-        install_name=$prefix/lib/liblucy.$major_version.dylib
-        ln -sf $lib_file "$install_name"
-        ln -sf $lib_file "$prefix/lib/liblucy.dylib"
-        install_name_tool -id "$install_name" "$prefix/lib/$lib_file"
-        ;;
-    *)
-        lib_file=liblucy.so.$version
-        if [ ! -f $lib_file ]; then
-            echo "$lib_file not found. Did you run make?"
-            exit 1
-        fi
-        mkdir -p "$prefix/lib"
-        cp $lib_file "$prefix/lib"
-        soname=liblucy.so.$major_version
-        ln -sf $lib_file "$prefix/lib/$soname"
-        ln -sf $soname "$prefix/lib/liblucy.so"
-        ;;
-esac
-
-# Install Clownfish header files.
-for src in `find ../core -name '*.cf[hp]'`; do
-    file=${src#../core/}
-    dest=$prefix/share/clownfish/include/$file
-    dir=`dirname "$dest"`
-    mkdir -p "$dir"
-    cp $src "$dest"
-done
-
-# Install man pages.
-mkdir -p "$prefix/man"
-# Resolve symbolic links.
-man_dir=$(cd "$prefix/man" && pwd -P)
-if [ -n "$man_dir" ]; then
-    cp -R autogen/man "$man_dir"
-else
-    echo "Warning: Invalid directory $prefix/man"
-fi
-
-# Create pkg-config file.
-mkdir -p "$prefix/lib/pkgconfig"
-cat <<EOF >"$prefix/lib/pkgconfig/lucy.pc"
-Name: Apache Lucy
-Description: Full-text search for dynamic languages
-Version: $version
-URL: http://lucy.apache.org/
-Requires: clownfish
-Libs: -L$prefix/lib -llucy -lclownfish
-EOF
-

http://git-wip-us.apache.org/repos/asf/lucy/blob/d6fcd5fb/common/charmonizer.c
----------------------------------------------------------------------
diff --git a/common/charmonizer.c b/common/charmonizer.c
index 64cbd6f..21641b1 100644
--- a/common/charmonizer.c
+++ b/common/charmonizer.c
@@ -680,7 +680,7 @@ typedef int
  * @param make_command Name of the make command. Auto-detect if NULL.
  */
 void
-chaz_Make_init(const char *make_command);
+chaz_Make_init(chaz_CLI *cli);
 
 /** Clean up the environment.
  */
@@ -742,6 +742,11 @@ chaz_MakeRule*
 chaz_MakeFile_add_rule(chaz_MakeFile *self, const char *target,
                        const char *prereq);
 
+/** Return the rule for the 'install' target.
+ */
+chaz_MakeRule*
+chaz_MakeFile_install_rule(chaz_MakeFile *self);
+
 /** Return the rule for the 'clean' target.
  */
 chaz_MakeRule*
@@ -756,10 +761,11 @@ chaz_MakeFile_distclean_rule(chaz_MakeFile *self);
  *
  * @param dir The target directory or NULL for the current directory.
  * @param basename The name of the executable without extension.
+ * @param installed Whether the executable will be installed.
  */
 chaz_MakeBinary*
 chaz_MakeFile_add_exe(chaz_MakeFile *self, const char *dir,
-                      const char *basename);
+                      const char *basename, int installed);
 
 /** Add a shared library. The library will be built in the current directory.
  * Returns a chaz_MakeBinary object.
@@ -768,21 +774,23 @@ chaz_MakeFile_add_exe(chaz_MakeFile *self, const char *dir,
  * @param basename The name of the library without prefix and extension.
  * @param version The version of the library.
  * @param major_version The major version of the library.
+ * @param installed Whether the library will be installed.
  */
 chaz_MakeBinary*
 chaz_MakeFile_add_shared_lib(chaz_MakeFile *self, const char *dir,
                              const char *basename, const char *version,
-                             const char *major_version);
+                             const char *major_version, int installed);
 
 /** Add a static library. The library will be built in the current directory.
  * Returns a chaz_MakeBinary object.
  *
  * @param dir The target directory or NULL for the current directory.
  * @param basename The name of the library without prefix and extension.
+ * @param installed Whether the library will be installed.
  */
 chaz_MakeBinary*
 chaz_MakeFile_add_static_lib(chaz_MakeFile *self, const char *dir,
-                             const char *basename);
+                             const char *basename, int installed);
 
 /** Add a rule to build the lemon parser generator.
  *
@@ -798,6 +806,41 @@ chaz_MakeFile_add_lemon_exe(chaz_MakeFile *self, const char *dir);
 chaz_MakeRule*
 chaz_MakeFile_add_lemon_grammar(chaz_MakeFile *self, const char *base_name);
 
+/** Add command to install rule that copies a file to a target directory.
+ *
+ * @param src Path to the file.
+ * @param root Destination root directory.
+ * @param dest Destination directory relative to root. May be NULL.
+ */
+void
+chaz_MakeFile_install(chaz_MakeFile *self, const char *src, const char *root,
+                      const char *dest);
+
+/** Add command to install rule that copies a directory to a target
+ * directory.
+ *
+ * @param src Path to the directory.
+ * @param root Destination root directory.
+ * @param dest Destination directory relative to root. May be NULL.
+ */
+void
+chaz_MakeFile_install_dir(chaz_MakeFile *self, const char *src,
+                          const char *root, const char *dest);
+
+/** Add command to install rule that creates a pkgconfig file in
+ * `$(LIBDIR)/pkgconfig`. The following pkgconfig variables are set:
+ *
+ * - `version`: Contains the value of the `version` argument.
+ * - `libdir`: Contains the value of $(LIBDIR).
+ *
+ * @param name Name of the pkgconfig file without extension.
+ * @param version The version.
+ * @param content The main content of the pkgconfig file.
+ */
+void
+chaz_MakeFile_install_pkgconfig(chaz_MakeFile *self, const char *name,
+                                const char *version, const char *content);
+
 /** Write the makefile to a file named 'Makefile' in the current directory.
  */
 void
@@ -832,6 +875,13 @@ chaz_MakeRule_add_prereq(chaz_MakeRule *self, const char *prereq);
 void
 chaz_MakeRule_add_command(chaz_MakeRule *self, const char *command);
 
+/** Add a command to create a directory.
+ *
+ * @param files The directory.
+ */
+void
+chaz_MakeRule_add_mkdir_command(chaz_MakeRule *self, const char *dir);
+
 /** Add a command to remove one or more files.
  *
  * @param files The list of files.
@@ -4555,6 +4605,7 @@ chaz_HeadCheck_maybe_add_to_cache(const char *header_name, int exists) {
 #include <string.h>
 /* #include "Charmonizer/Core/Make.h" */
 /* #include "Charmonizer/Core/CFlags.h" */
+/* #include "Charmonizer/Core/CLI.h" */
 /* #include "Charmonizer/Core/Compiler.h" */
 /* #include "Charmonizer/Core/OperatingSystem.h" */
 /* #include "Charmonizer/Core/Util.h" */
@@ -4576,22 +4627,22 @@ struct chaz_MakeRule {
 };
 
 struct chaz_MakeBinary {
-    int             type;
-    char           *target_dir;
-    char           *basename;
-    char           *version;
-    char           *major_version;
+    chaz_MakeRule  *rule;  /* Owned by MakeBinary. */
+
+    chaz_MakeVar   *obj_var;
+    char           *obj_dollar_var;
     char          **sources;  /* List of all sources. */
     size_t          num_sources;
     char          **single_sources;  /* Only sources from add_src_file. */
     size_t          num_single_sources;
     char          **dirs;
     size_t          num_dirs;
-    chaz_MakeVar   *obj_var;  /* Owned by MakeFile. */
-    char           *dollar_var;
-    chaz_MakeRule  *rule;  /* Not added to MakeFile, owned by MakeBinary. */
-    chaz_CFlags    *compile_flags;
-    chaz_CFlags    *link_flags;
+
+    chaz_MakeVar   *cflags_var;
+    chaz_CFlags    *cflags;
+
+    chaz_MakeVar   *ldflags_var;
+    chaz_CFlags    *ldflags;
 };
 
 struct chaz_MakeFile {
@@ -4599,6 +4650,9 @@ struct chaz_MakeFile {
     size_t            num_vars;
     chaz_MakeRule   **rules;
     size_t            num_rules;
+    char            **install_dirs;
+    size_t            num_install_dirs;
+    chaz_MakeRule    *install;
     chaz_MakeRule    *clean;
     chaz_MakeRule    *distclean;
     chaz_MakeBinary **binaries;
@@ -4613,11 +4667,12 @@ typedef struct {
 
 /* Static vars. */
 static struct {
-    char *make_command;
-    int   shell_type;
-    int   supports_pattern_rules;
+    chaz_CLI *cli;
+    char     *make_command;
+    int       shell_type;
+    int       supports_pattern_rules;
 } chaz_Make = {
-    NULL,
+    NULL, NULL,
     0, 0
 };
 
@@ -4638,24 +4693,18 @@ S_chaz_Make_detect(const char *make1, ...);
 static int
 S_chaz_Make_audition(const char *make);
 
-static void
-S_chaz_MakeFile_finish_exe(chaz_MakeFile *self, chaz_MakeBinary *binary);
+static chaz_MakeBinary*
+S_chaz_MakeFile_add_binary(chaz_MakeFile *self, int type, const char *basename,
+                           const char *target);
 
 static void
-S_chaz_MakeFile_finish_shared_lib(chaz_MakeFile *self,
-                                  chaz_MakeBinary *binary);
+S_chaz_MakeFile_add_install_dir(chaz_MakeFile *self, const char *dir);
 
 static void
-S_chaz_MakeFile_finish_static_lib(chaz_MakeFile *self,
-                                  chaz_MakeBinary *binary);
-
-static chaz_MakeBinary*
-S_chaz_MakeFile_add_binary(chaz_MakeFile *self, int type, const char *dir,
-                           const char *basename, const char *target);
+S_chaz_MakeFile_write_install_vars(FILE *out);
 
 static void
-S_chaz_MakeFile_write_binary_rules(chaz_MakeFile *self,
-                                   chaz_MakeBinary *binary, FILE *out);
+S_chaz_MakeFile_write_binary_rules(chaz_MakeBinary *binary, FILE *out);
 
 static void
 S_chaz_MakeFile_write_object_rules(char **sources, const char *command,
@@ -4691,7 +4740,10 @@ static char*
 S_chaz_MakeBinary_obj_path(const char *src_path);
 
 void
-chaz_Make_init(const char *make_command) {
+chaz_Make_init(chaz_CLI *cli) {
+    const char *make_command = chaz_CLI_strval(cli, "make");
+
+    chaz_Make.cli        = cli;
     chaz_Make.shell_type = chaz_OS_shell_type();
 
     if (make_command) {
@@ -4811,13 +4863,16 @@ chaz_MakeFile_new() {
     chaz_MakeFile *self = (chaz_MakeFile*)calloc(1, sizeof(chaz_MakeFile));
     char *generated;
 
-    self->vars     = (chaz_MakeVar**)calloc(1, sizeof(chaz_MakeVar*));
-    self->rules    = (chaz_MakeRule**)calloc(1, sizeof(chaz_MakeRule*));
-    self->binaries = (chaz_MakeBinary**)calloc(1, sizeof(chaz_MakeBinary*));
+    self->vars         = (chaz_MakeVar**)calloc(1, sizeof(chaz_MakeVar*));
+    self->rules        = (chaz_MakeRule**)calloc(1, sizeof(chaz_MakeRule*));
+    self->install_dirs = (char**)calloc(1, sizeof(char*));
 
+    self->install   = S_chaz_MakeRule_new("install", "all");
     self->clean     = S_chaz_MakeRule_new("clean", NULL);
     self->distclean = S_chaz_MakeRule_new("distclean", "clean");
 
+    self->binaries = (chaz_MakeBinary**)calloc(1, sizeof(chaz_MakeBinary*));
+
     /* MSVC leaves .obj files around when creating executables. */
     generated = chaz_Util_join("", "charmonizer", chaz_OS_exe_ext(),
                                " charmonizer.obj charmony.h Makefile", NULL);
@@ -4844,11 +4899,17 @@ chaz_MakeFile_destroy(chaz_MakeFile *self) {
     }
     free(self->rules);
 
+    for (i = 0; self->install_dirs[i]; i++) {
+        free(self->install_dirs[i]);
+    }
+    free(self->install_dirs);
+
     for (i = 0; self->binaries[i]; i++) {
         S_chaz_MakeBinary_destroy(self->binaries[i]);
     }
     free(self->binaries);
 
+    S_chaz_MakeRule_destroy(self->install);
     S_chaz_MakeRule_destroy(self->clean);
     S_chaz_MakeRule_destroy(self->distclean);
 
@@ -4896,6 +4957,11 @@ chaz_MakeFile_add_rule(chaz_MakeFile *self, const char *target,
 }
 
 chaz_MakeRule*
+chaz_MakeFile_install_rule(chaz_MakeFile *self) {
+    return self->install;
+}
+
+chaz_MakeRule*
 chaz_MakeFile_clean_rule(chaz_MakeFile *self) {
     return self->clean;
 }
@@ -4907,10 +4973,12 @@ chaz_MakeFile_distclean_rule(chaz_MakeFile *self) {
 
 chaz_MakeBinary*
 chaz_MakeFile_add_exe(chaz_MakeFile *self, const char *dir,
-                      const char *basename) {
+                      const char *basename, int installed) {
     const char *exe_ext = chaz_CC_exe_ext();
     char *target;
+    char *command;
     chaz_MakeBinary *binary;
+    chaz_CFlags *ldflags;
 
     if (dir == NULL || strcmp(dir, ".") == 0) {
         target = chaz_Util_join("", basename, exe_ext, NULL);
@@ -4920,179 +4988,190 @@ chaz_MakeFile_add_exe(chaz_MakeFile *self, const char *dir,
         target = chaz_Util_join("", dir, dir_sep, basename, exe_ext, NULL);
     }
 
-    binary = S_chaz_MakeFile_add_binary(self, CHAZ_MAKEBINARY_EXE, dir,
-                                        basename, target);
-
-    free(target);
-    return binary;
-}
+    binary = S_chaz_MakeFile_add_binary(self, CHAZ_MAKEBINARY_EXE, basename,
+                                        target);
 
-void
-S_chaz_MakeFile_finish_exe(chaz_MakeFile *self, chaz_MakeBinary *binary) {
-    const char *link_flags_string;
-    char *command;
+    ldflags = chaz_CC_new_cflags();
+    if (chaz_CC_is_msvc()) {
+        chaz_CFlags_append(ldflags, "/nologo");
+    }
+    chaz_CFlags_set_link_output(ldflags, "$@");
 
-    (void)self;
+    /* Objects must come before flags since flags may contain libraries. */
+    command = chaz_Util_join("", "$(LINK) ", chaz_CFlags_get_string(ldflags),
+                             " ", binary->obj_dollar_var,
+                             " $(", binary->ldflags_var->name, ") ", NULL);
+    chaz_MakeRule_add_command(binary->rule, command);
 
-    /* This is destructive but shouldn't be a problem since a Makefile
-     * is only written once.
-     */
-    chaz_CFlags_set_link_output(binary->link_flags, "$@");
-    link_flags_string = chaz_CFlags_get_string(binary->link_flags);
+    if (installed) {
+        chaz_MakeFile_install(self, target, "$(BINDIR)", NULL);
+    }
 
-    /* Objects in dollar var must come before flags since flags may
-     * contain libraries.
-     */
-    command = chaz_Util_join(" ", "$(LINK)", binary->dollar_var,
-                             link_flags_string, NULL);
-    chaz_MakeRule_add_command(binary->rule, command);
+    chaz_CFlags_destroy(ldflags);
     free(command);
+    free(target);
+    return binary;
 }
 
 chaz_MakeBinary*
 chaz_MakeFile_add_shared_lib(chaz_MakeFile *self, const char *dir,
                              const char *basename, const char *version,
-                             const char *major_version) {
-    int binary_format = chaz_CC_binary_format();
-    char *target;
+                             const char *major_version, int installed) {
+    int binfmt = chaz_CC_binary_format();
+    char *path, *vpath, *mpath;
+    char *command;
     chaz_MakeBinary *binary;
+    chaz_CFlags *ldflags;
 
-    if (binary_format == CHAZ_CC_BINFMT_PE) {
-        target = chaz_CC_shared_lib_filename(dir, basename, major_version);
+    path = chaz_CC_shared_lib_filename(dir, basename, NULL);
+    if (binfmt == CHAZ_CC_BINFMT_PE) {
+        vpath = chaz_CC_shared_lib_filename(dir, basename, major_version);
+        mpath = NULL;
     }
     else {
-        target = chaz_CC_shared_lib_filename(dir, basename, version);
+        vpath = chaz_CC_shared_lib_filename(dir, basename, version);
+        mpath = chaz_CC_shared_lib_filename(dir, basename, major_version);
     }
 
-    binary = S_chaz_MakeFile_add_binary(self, CHAZ_MAKEBINARY_SHARED_LIB, dir,
-                                        basename, target);
-    binary->version       = chaz_Util_strdup(version);
-    binary->major_version = chaz_Util_strdup(major_version);
-
-    chaz_CFlags_compile_shared_library(binary->compile_flags);
-    chaz_CFlags_link_shared_library(binary->link_flags, basename, version,
-                                    major_version);
-
-    free(target);
-    return binary;
-}
+    binary = S_chaz_MakeFile_add_binary(self, CHAZ_MAKEBINARY_SHARED_LIB,
+                                        basename, vpath);
 
-void
-S_chaz_MakeFile_finish_shared_lib(chaz_MakeFile *self,
-                                  chaz_MakeBinary *binary) {
-    const char *link_flags_string;
-    int binfmt = chaz_CC_binary_format();
-    char *no_v_name
-        = chaz_CC_shared_lib_filename(binary->target_dir, binary->basename,
-                                      NULL);
-    char *major_v_name
-        = chaz_CC_shared_lib_filename(binary->target_dir, binary->basename,
-                                      binary->major_version);
-    char *command;
+    chaz_CFlags_compile_shared_library(binary->cflags);
 
+    ldflags = chaz_CC_new_cflags();
+    if (chaz_CC_is_msvc()) {
+        chaz_CFlags_append(ldflags, "/nologo");
+    }
+    chaz_CFlags_set_link_output(ldflags, "$@");
+    chaz_CFlags_link_shared_library(ldflags, basename, version, major_version);
     if (binfmt == CHAZ_CC_BINFMT_MACHO) {
-        const char *dir_sep = chaz_OS_dir_sep();
-        char *install_name;
-
         /* Set temporary install name with full path on Darwin. */
-        install_name = chaz_Util_join("", "-install_name $(CURDIR)", dir_sep,
-                                      major_v_name, NULL);
-        chaz_CFlags_append(binary->link_flags, install_name);
+        char *install_name = chaz_Util_join("", "-install_name \"$(CURDIR)/",
+                                            mpath, "\"", NULL);
+        chaz_CFlags_append(ldflags, install_name);
         free(install_name);
     }
 
-    chaz_CFlags_set_link_output(binary->link_flags, "$@");
-    link_flags_string = chaz_CFlags_get_string(binary->link_flags);
-
-    command = chaz_Util_join(" ", "$(LINK)", binary->dollar_var,
-                             link_flags_string, NULL);
+    command = chaz_Util_join("", "$(LINK) ", chaz_CFlags_get_string(ldflags),
+                             " ", binary->obj_dollar_var,
+                             " $(", binary->ldflags_var->name, ")", NULL);
     chaz_MakeRule_add_command(binary->rule, command);
     free(command);
 
+    if (installed) {
+        const char *root = binfmt == CHAZ_CC_BINFMT_PE
+                           ? "$(BINDIR)" : "$(LIBDIR)";
+        chaz_MakeFile_install(self, vpath, root, NULL);
+    }
+
     /* Add symlinks. */
     if (binfmt == CHAZ_CC_BINFMT_ELF || binfmt == CHAZ_CC_BINFMT_MACHO) {
-        command = chaz_Util_join(" ", "ln -sf", binary->rule->targets,
-                                 major_v_name, NULL);
+        char *name  = chaz_CC_shared_lib_filename(NULL, basename, NULL);
+        char *vname = chaz_CC_shared_lib_filename(NULL, basename, version);
+        char *mname = chaz_CC_shared_lib_filename(NULL, basename,
+                                                  major_version);
+        const char *ltarget = binfmt == CHAZ_CC_BINFMT_MACHO ? vname : mname;
+
+        command = chaz_Util_join(" ", "ln -sf", vname, mpath, NULL);
         chaz_MakeRule_add_command(binary->rule, command);
         free(command);
 
-        if (binfmt == CHAZ_CC_BINFMT_MACHO) {
-            command = chaz_Util_join(" ", "ln -sf", binary->rule->targets,
-                                     no_v_name, NULL);
-        }
-        else {
-            command = chaz_Util_join(" ", "ln -sf", major_v_name, no_v_name,
-                                     NULL);
-        }
+        command = chaz_Util_join(" ", "ln -sf", ltarget, path, NULL);
         chaz_MakeRule_add_command(binary->rule, command);
         free(command);
 
-        chaz_MakeRule_add_rm_command(self->clean, major_v_name);
-        chaz_MakeRule_add_rm_command(self->clean, no_v_name);
+        if (installed) {
+            command = chaz_Util_join("", "ln -sf ", vname,
+                                     " \"$(LIBDIR)/", mname, "\"",
+                                     NULL);
+            chaz_MakeRule_add_command(self->install, command);
+            free(command);
+
+            command = chaz_Util_join("", "ln -sf ", ltarget,
+                                     " \"$(LIBDIR)/", name, "\"",
+                                     NULL);
+            chaz_MakeRule_add_command(self->install, command);
+            free(command);
+
+            if (binfmt == CHAZ_CC_BINFMT_MACHO) {
+                /* Change install name to installation path. */
+                command = chaz_Util_join("",
+                    "install_name_tool",
+                    " -id \"$(LIBDIR)/", mname, "\"",
+                    " \"$(LIBDIR)/", vname, "\"",
+                    NULL
+                );
+                chaz_MakeRule_add_command(self->install, command);
+                free(command);
+            }
+        }
+
+        chaz_MakeRule_add_rm_command(self->clean, mpath);
+        chaz_MakeRule_add_rm_command(self->clean, path);
+
+        free(mname);
+        free(vname);
+        free(name);
     }
 
     if (binfmt == CHAZ_CC_BINFMT_PE) {
-        /* Remove import library. */
-        char *filename
-            = chaz_CC_import_lib_filename(binary->target_dir, binary->basename,
-                                          binary->major_version);
+        /* Import library. */
+        char *filename = chaz_CC_import_lib_filename(dir, basename,
+                                                     major_version);
+        if (installed) {
+            chaz_MakeFile_install(self, filename, "$(LIBDIR)", NULL);
+        }
         chaz_MakeRule_add_rm_command(self->clean, filename);
         free(filename);
     }
 
     if (chaz_CC_is_msvc()) {
         /* Remove export file. */
-        char *filename
-            = chaz_CC_export_filename(binary->target_dir, binary->basename,
-                                      binary->major_version);
+        char *filename = chaz_CC_export_filename(dir, basename, major_version);
         chaz_MakeRule_add_rm_command(self->clean, filename);
         free(filename);
     }
 
-    free(major_v_name);
-    free(no_v_name);
+    chaz_CFlags_destroy(ldflags);
+    free(mpath);
+    free(vpath);
+    free(path);
+    return binary;
 }
 
 chaz_MakeBinary*
 chaz_MakeFile_add_static_lib(chaz_MakeFile *self, const char *dir,
-                             const char *basename) {
+                             const char *basename, int installed) {
     char *target = chaz_CC_static_lib_filename(dir, basename);
+    char *command;
     chaz_MakeBinary *binary
-        = S_chaz_MakeFile_add_binary(self, CHAZ_MAKEBINARY_STATIC_LIB, dir,
+        = S_chaz_MakeFile_add_binary(self, CHAZ_MAKEBINARY_STATIC_LIB,
                                      basename, target);
 
-    free(target);
-    return binary;
-}
-
-static void
-S_chaz_MakeFile_finish_static_lib(chaz_MakeFile *self,
-                                  chaz_MakeBinary *binary) {
-    char *command;
-
-    (void)self;
-
-    command = chaz_CC_format_archiver_command("$@", binary->dollar_var);
+    command = chaz_CC_format_archiver_command("$@", binary->obj_dollar_var);
     chaz_MakeRule_add_command(binary->rule, command);
-    free(command);
 
-    command = chaz_CC_format_ranlib_command("$@");
-    if (command) {
-        chaz_MakeRule_add_command(binary->rule, command);
-        free(command);
+    if (installed) {
+        chaz_MakeFile_install(self, target, "$(LIBDIR)", NULL);
     }
+
+    free(command);
+    free(target);
+    return binary;
 }
 
 static chaz_MakeBinary*
-S_chaz_MakeFile_add_binary(chaz_MakeFile *self, int type, const char *dir,
-                           const char *basename, const char *target) {
+S_chaz_MakeFile_add_binary(chaz_MakeFile *self, int type, const char *basename,
+                           const char *target) {
     chaz_MakeBinary *binary
         = (chaz_MakeBinary*)calloc(1, sizeof(chaz_MakeBinary));
     const char *suffix;
-    char *uc_basename = chaz_Util_strdup(basename);
+    char *uc_base = chaz_Util_strdup(basename);
     char *binary_var_name;
     char *obj_var_name;
-    char *dollar_var;
+    char *cflags_var_name;
+    char *ldflags_var_name;
+    char *obj_dollar_var;
     size_t i;
     size_t num_binaries;
     size_t alloc_size;
@@ -5107,27 +5186,32 @@ S_chaz_MakeFile_add_binary(chaz_MakeFile *self, int type, const char *dir,
             return NULL;
     }
 
-    for (i = 0; uc_basename[i] != '\0'; i++) {
-        uc_basename[i] = toupper((unsigned char)uc_basename[i]);
+    for (i = 0; uc_base[i] != '\0'; i++) {
+        uc_base[i] = toupper((unsigned char)uc_base[i]);
     }
 
-    binary_var_name = chaz_Util_join("_", uc_basename, suffix, NULL);
-    obj_var_name    = chaz_Util_join("_", uc_basename, suffix, "OBJS", NULL);
-    dollar_var      = chaz_Util_join("", "$(", obj_var_name, ")", NULL);
+    binary_var_name  = chaz_Util_join("_", uc_base, suffix, NULL);
+    obj_var_name     = chaz_Util_join("_", uc_base, suffix, "OBJS", NULL);
+    cflags_var_name  = chaz_Util_join("_", uc_base, suffix, "CFLAGS", NULL);
+    ldflags_var_name = chaz_Util_join("_", uc_base, suffix, "LDFLAGS", NULL);
+    obj_dollar_var   = chaz_Util_join("", "$(", obj_var_name, ")", NULL);
 
     chaz_MakeFile_add_var(self, binary_var_name, target);
 
-    binary->type           = type;
-    binary->target_dir     = dir ? chaz_Util_strdup(dir) : NULL;
-    binary->basename       = chaz_Util_strdup(basename);
+    binary->rule           = S_chaz_MakeRule_new(target, obj_dollar_var);
     binary->obj_var        = chaz_MakeFile_add_var(self, obj_var_name, NULL);
-    binary->dollar_var     = dollar_var;
-    binary->rule           = S_chaz_MakeRule_new(target, dollar_var);
+    binary->obj_dollar_var = obj_dollar_var;
     binary->sources        = (char**)calloc(1, sizeof(char*));
     binary->single_sources = (char**)calloc(1, sizeof(char*));
     binary->dirs           = (char**)calloc(1, sizeof(char*));
-    binary->compile_flags  = chaz_CC_new_cflags();
-    binary->link_flags     = chaz_CC_new_cflags();
+
+    binary->cflags_var  = chaz_MakeFile_add_var(self, cflags_var_name, NULL);
+    binary->cflags      = chaz_CC_new_cflags();
+    binary->ldflags_var = chaz_MakeFile_add_var(self, ldflags_var_name, NULL);
+    binary->ldflags     = chaz_CC_new_cflags();
+
+    chaz_MakeRule_add_rm_command(self->clean, obj_dollar_var);
+    chaz_MakeRule_add_rm_command(self->clean, target);
 
     num_binaries = self->num_binaries;
     alloc_size   = (num_binaries + 2) * sizeof(chaz_MakeBinary*);
@@ -5137,15 +5221,17 @@ S_chaz_MakeFile_add_binary(chaz_MakeFile *self, int type, const char *dir,
     self->binaries     = binaries;
     self->num_binaries = num_binaries + 1;
 
-    free(uc_basename);
+    free(ldflags_var_name);
+    free(cflags_var_name);
     free(obj_var_name);
     free(binary_var_name);
+    free(uc_base);
     return binary;
 }
 
 chaz_MakeBinary*
 chaz_MakeFile_add_lemon_exe(chaz_MakeFile *self, const char *dir) {
-    chaz_MakeBinary *exe = chaz_MakeFile_add_exe(self, dir, "lemon");
+    chaz_MakeBinary *exe = chaz_MakeFile_add_exe(self, dir, "lemon", 0);
     chaz_MakeBinary_add_src_file(exe, dir, "lemon.c");
     return exe;
 }
@@ -5175,6 +5261,147 @@ chaz_MakeFile_add_lemon_grammar(chaz_MakeFile *self,
 }
 
 void
+chaz_MakeFile_install(chaz_MakeFile *self, const char *src, const char *root,
+                      const char *dest) {
+    char *path;
+    char *command;
+
+    if (dest) {
+        path = chaz_Util_join(chaz_OS_dir_sep(), root, dest, NULL);
+    }
+    else {
+        path = chaz_Util_strdup(root);
+    }
+
+    S_chaz_MakeFile_add_install_dir(self, path);
+
+    if (chaz_Make.shell_type == CHAZ_OS_POSIX) {
+        command = chaz_Util_join("", "cp -f ", src, " \"", path, "\"", NULL);
+    }
+    else if (chaz_Make.shell_type == CHAZ_OS_CMD_EXE) {
+        command = chaz_Util_join("", "copy /y ", src, " \"", path, "\" >nul",
+                                 NULL);
+    }
+    else {
+        chaz_Util_die("Unsupported shell type: %d", chaz_Make.shell_type);
+    }
+
+    chaz_MakeRule_add_command(self->install, command);
+
+    free(command);
+    free(path);
+}
+
+void
+chaz_MakeFile_install_dir(chaz_MakeFile *self, const char *src,
+                          const char *root, const char *dest) {
+    char *path;
+    char *command;
+
+    if (dest) {
+        path = chaz_Util_join(chaz_OS_dir_sep(), root, dest, NULL);
+    }
+    else {
+        path = chaz_Util_strdup(root);
+    }
+
+    S_chaz_MakeFile_add_install_dir(self, path);
+
+    if (chaz_Make.shell_type == CHAZ_OS_POSIX) {
+        command = chaz_Util_join("", "cp -Rf ", src, "/* \"", path, "\"",
+                                 NULL);
+    }
+    else if (chaz_Make.shell_type == CHAZ_OS_CMD_EXE) {
+        command = chaz_Util_join("", "xcopy /seiy ", src, " \"", path,
+                                 "\" >nul", NULL);
+    }
+    else {
+        chaz_Util_die("Unsupported shell type: %d", chaz_Make.shell_type);
+    }
+
+    chaz_MakeRule_add_command(self->install, command);
+
+    free(command);
+    free(path);
+}
+
+void
+chaz_MakeFile_install_pkgconfig(chaz_MakeFile *self, const char *name,
+                                const char *version, const char *content) {
+    size_t size;
+    const char *p;
+    char *q;
+    char *escaped;
+    char *command;
+
+    if (chaz_OS_shell_type() != CHAZ_OS_POSIX) { return; }
+
+    /* Escape content for POSIX printf utility. */
+
+    for (p = content, size = 0; *p != '\0'; p++) {
+        switch (*p) {
+            case '%':  size += 2; break;
+            case '\\': size += 2; break;
+            case '\'': size += 4; break;
+            case '\n': size += 2; break;
+            default:   size += 1; break;
+        }
+    }
+
+    escaped = (char*)malloc(size + 1);
+
+    for (p = content, q = escaped; *p != '\0'; p++) {
+        switch (*p) {
+            case '%':  memcpy(q, "%%",    2); q += 2; break;
+            case '\\': memcpy(q, "\\\\",  2); q += 2; break;
+            case '\'': memcpy(q, "\\047", 4); q += 4; break;
+            case '\n': memcpy(q, "\\n",   2); q += 2; break;
+            default: *q++ = *p; break;
+        }
+    }
+
+    *q++ = '\0';
+
+    S_chaz_MakeFile_add_install_dir(self, "$(LIBDIR)/pkgconfig");
+
+    command = chaz_Util_join("",
+        "printf '"
+            "libdir=$(LIBDIR)\\n"
+            "version=", version, "\\n"
+            "\\n",
+            escaped,
+        "' >\"$(LIBDIR)/pkgconfig/", name, ".pc\"",
+        NULL
+    );
+    chaz_MakeRule_add_command(self->install, command);
+
+    free(command);
+    free(escaped);
+}
+
+static void
+S_chaz_MakeFile_add_install_dir(chaz_MakeFile *self, const char *dir) {
+    size_t   i;
+    size_t   num_install_dirs;
+    size_t   alloc_size;
+    char   **install_dirs;
+
+    for (i = 0; self->install_dirs[i]; i++) {
+        if (strcmp(dir, self->install_dirs[i]) == 0) {
+            return;
+        }
+    }
+
+    num_install_dirs = self->num_install_dirs;
+    alloc_size       = (num_install_dirs + 2) * sizeof(char*);
+    install_dirs     = (char**)realloc(self->install_dirs, alloc_size);
+    install_dirs[num_install_dirs]   = chaz_Util_strdup(dir);
+    install_dirs[num_install_dirs+1] = NULL;
+    self->install_dirs     = install_dirs;
+    self->num_install_dirs = num_install_dirs + 1;
+}
+
+void
 chaz_MakeFile_write(chaz_MakeFile *self) {
     FILE   *out;
     size_t  i;
@@ -5192,6 +5419,19 @@ chaz_MakeFile_write(chaz_MakeFile *self) {
     fprintf(out, "CC = %s\n", chaz_CC_get_cc());
     fprintf(out, "LINK = %s\n", chaz_CC_link_command());
 
+    S_chaz_MakeFile_write_install_vars(out);
+
+    /* Finalize binary vars. */
+    for (i = 0; self->binaries[i]; i++) {
+        chaz_MakeBinary *binary = self->binaries[i];
+        const char *flags;
+
+        flags = chaz_CFlags_get_string(binary->cflags);
+        chaz_MakeVar_append(binary->cflags_var, flags);
+        flags = chaz_CFlags_get_string(binary->ldflags);
+        chaz_MakeVar_append(binary->ldflags_var, flags);
+    }
+
     for (i = 0; self->vars[i]; i++) {
         chaz_MakeVar *var = self->vars[i];
         fprintf(out, "%s = %s\n", var->name, var->value);
@@ -5203,9 +5443,27 @@ chaz_MakeFile_write(chaz_MakeFile *self) {
     }
 
     for (i = 0; self->binaries[i]; i++) {
-        S_chaz_MakeFile_write_binary_rules(self, self->binaries[i], out);
+        S_chaz_MakeFile_write_binary_rules(self->binaries[i], out);
     }
 
+    if (self->num_install_dirs) {
+        /* Prepend mkdir commands. */
+        chaz_MakeRule *dummy = S_chaz_MakeRule_new(NULL, NULL);
+        char *commands;
+
+        for (i = 0; self->install_dirs[i]; i++) {
+            chaz_MakeRule_add_mkdir_command(dummy, self->install_dirs[i]);
+        }
+
+        commands = chaz_Util_join("", dummy->commands, self->install->commands,
+                                  NULL);
+        free(self->install->commands);
+        self->install->commands = commands;
+
+        S_chaz_MakeRule_destroy(dummy);
+    }
+
+    S_chaz_MakeRule_write(self->install, out);
     S_chaz_MakeRule_write(self->clean, out);
     S_chaz_MakeRule_write(self->distclean, out);
 
@@ -5223,39 +5481,62 @@ chaz_MakeFile_write(chaz_MakeFile *self) {
 }
 
 static void
-S_chaz_MakeFile_write_binary_rules(chaz_MakeFile *self,
-                                   chaz_MakeBinary *binary, FILE *out) {
-    const char *cflags;
+S_chaz_MakeFile_write_install_vars(FILE *out) {
+    const char *dir_sep = chaz_OS_dir_sep();
+    const char *strval;
 
-    if (chaz_CC_is_msvc()) {
-        chaz_CFlags_append(binary->compile_flags, "/nologo");
-        chaz_CFlags_append(binary->link_flags, "/nologo");
+    strval = chaz_CLI_strval(chaz_Make.cli, "prefix");
+    fprintf(out, "PREFIX = %s\n", strval ? strval : "/usr/local");
+
+    strval = chaz_CLI_strval(chaz_Make.cli, "bindir");
+    if (strval) {
+        fprintf(out, "BINDIR = %s\n", strval);
+    }
+    else {
+        fprintf(out, "BINDIR = $(PREFIX)%sbin\n", dir_sep);
     }
 
-    switch (binary->type) {
-        case CHAZ_MAKEBINARY_EXE:
-            S_chaz_MakeFile_finish_exe(self, binary);
-            break;
-        case CHAZ_MAKEBINARY_STATIC_LIB:
-            S_chaz_MakeFile_finish_static_lib(self, binary);
-            break;
-        case CHAZ_MAKEBINARY_SHARED_LIB:
-            S_chaz_MakeFile_finish_shared_lib(self, binary);
-            break;
-        default:
-            chaz_Util_die("Invalid binary type: %d", binary->type);
-            return;
+    strval = chaz_CLI_strval(chaz_Make.cli, "datarootdir");
+    if (strval) {
+        fprintf(out, "DATAROOTDIR = %s\n", strval);
+    }
+    else {
+        fprintf(out, "DATAROOTDIR = $(PREFIX)%sshare\n", dir_sep);
     }
 
-    chaz_MakeRule_add_rm_command(self->clean, binary->rule->targets);
-    chaz_MakeRule_add_rm_command(self->clean, binary->dollar_var);
+    strval = chaz_CLI_strval(chaz_Make.cli, "datadir");
+    fprintf(out, "DATADIR = %s\n", strval ? strval : "$(DATAROOTDIR)");
+
+    strval = chaz_CLI_strval(chaz_Make.cli, "libdir");
+    if (strval) {
+        fprintf(out, "LIBDIR = %s\n", strval);
+    }
+    else {
+        fprintf(out, "LIBDIR = $(PREFIX)%slib\n", dir_sep);
+    }
+
+    strval = chaz_CLI_strval(chaz_Make.cli, "mandir");
+    if (strval) {
+        fprintf(out, "MANDIR = %s\n", strval);
+    }
+    else {
+        fprintf(out, "MANDIR = $(DATAROOTDIR)%sman\n", dir_sep);
+    }
+}
+
+static void
+S_chaz_MakeFile_write_binary_rules(chaz_MakeBinary *binary, FILE *out) {
+    const char *cflags_string;
 
     S_chaz_MakeRule_write(binary->rule, out);
 
-    cflags = chaz_CFlags_get_string(binary->compile_flags);
+    cflags_string = chaz_CFlags_get_string(binary->cflags);
 
     /* Write rules to compile with custom flags. */
-    if (cflags[0] != '\0') {
+    if (cflags_string[0] != '\0') {
+        char *dollar_var = chaz_Util_join("", "$(", binary->cflags_var->name,
+                                          ")", NULL);
+
         if (!chaz_Make.supports_pattern_rules
             || chaz_Make.shell_type == CHAZ_OS_CMD_EXE) {
             /* Write a rule for each object file. This is needed for make
@@ -5263,21 +5544,25 @@ S_chaz_MakeFile_write_binary_rules(chaz_MakeFile *self,
              * mingw32-make which has problems with pattern rules and
              * backslash directory separators.
              */
-            S_chaz_MakeFile_write_object_rules(binary->sources, cflags, out);
+            S_chaz_MakeFile_write_object_rules(binary->sources, dollar_var,
+                                               out);
         }
         else {
             /* Write a pattern rule for each directory. */
-            S_chaz_MakeFile_write_pattern_rules(binary->dirs, cflags, out);
+            S_chaz_MakeFile_write_pattern_rules(binary->dirs, dollar_var, out);
             /* Write a rule for each object added with add_src_file. */
-            S_chaz_MakeFile_write_object_rules(binary->single_sources, cflags,
-                                               out);
+            S_chaz_MakeFile_write_object_rules(binary->single_sources,
+                                               dollar_var, out);
         }
+
+        free(dollar_var);
     }
 }
 
 static void
 S_chaz_MakeFile_write_object_rules(char **sources, const char *cflags,
                                    FILE *out) {
+    const char *cc = chaz_CC_is_msvc() ? "$(CC) /nologo" : "$(CC)";
     chaz_CFlags *output_cflags = chaz_CC_new_cflags();
     const char *output_cflags_string;
     size_t i;
@@ -5294,7 +5579,7 @@ S_chaz_MakeFile_write_object_rules(char **sources, const char *cflags,
         if (obj_path == NULL) { continue; }
 
         rule = S_chaz_MakeRule_new(obj_path, source);
-        command = chaz_Util_join(" ", "$(CC) $(CFLAGS)", cflags, source,
+        command = chaz_Util_join(" ", cc, "$(CFLAGS)", cflags, source,
                                  output_cflags_string, NULL);
         chaz_MakeRule_add_command(rule, command);
         S_chaz_MakeRule_write(rule, out);
@@ -5310,6 +5595,7 @@ S_chaz_MakeFile_write_object_rules(char **sources, const char *cflags,
 static void
 S_chaz_MakeFile_write_pattern_rules(char **dirs, const char *cflags,
                                     FILE *out) {
+    const char *cc = chaz_CC_is_msvc() ? "$(CC) /nologo" : "$(CC)";
     const char *obj_ext = chaz_CC_obj_ext();
     const char *dir_sep = chaz_OS_dir_sep();
     chaz_CFlags *output_cflags = chaz_CC_new_cflags();
@@ -5319,7 +5605,7 @@ S_chaz_MakeFile_write_pattern_rules(char **dirs, const char *cflags,
 
     chaz_CFlags_set_output_obj(output_cflags, "$@");
     output_cflags_string = chaz_CFlags_get_string(output_cflags);
-    command  = chaz_Util_join(" ", "$(CC) $(CFLAGS)", cflags, "$<",
+    command  = chaz_Util_join(" ", cc, "$(CFLAGS)", cflags, "$<",
                               output_cflags_string, NULL);
 
     for (i = 0; dirs[i]; i++) {
@@ -5449,6 +5735,25 @@ chaz_MakeRule_add_command(chaz_MakeRule *self, const char *command) {
 }
 
 void
+chaz_MakeRule_add_mkdir_command(chaz_MakeRule *self, const char *dir) {
+    char *command;
+
+    if (chaz_Make.shell_type == CHAZ_OS_POSIX) {
+        command = chaz_Util_join("", "mkdir -p \"", dir, "\"", NULL);
+    }
+    else if (chaz_Make.shell_type == CHAZ_OS_CMD_EXE) {
+        command = chaz_Util_join("", "if not exist \"", dir, "\" mkdir \"",
+                                 dir, "\"", NULL);
+    }
+    else {
+        chaz_Util_die("Unsupported shell type: %d", chaz_Make.shell_type);
+    }
+
+    chaz_MakeRule_add_command(self, command);
+    free(command);
+}
+
+void
 chaz_MakeRule_add_rm_command(chaz_MakeRule *self, const char *files) {
     char *command;
 
@@ -5499,8 +5804,6 @@ chaz_MakeRule_add_make_command(chaz_MakeRule *self, const char *dir,
             command = chaz_Util_join("", "(cd ", dir, " && $(MAKE) ", target,
                                      ")", NULL);
         }
-        chaz_MakeRule_add_command(self, command);
-        free(command);
     }
     else if (chaz_Make.shell_type == CHAZ_OS_CMD_EXE) {
         if (!target) {
@@ -5511,23 +5814,20 @@ chaz_MakeRule_add_make_command(chaz_MakeRule *self, const char *dir,
             command = chaz_Util_join(" ", "pushd", dir, "&& $(MAKE)", target,
                                      "&& popd", NULL);
         }
-        chaz_MakeRule_add_command(self, command);
-        free(command);
     }
     else {
         chaz_Util_die("Unsupported shell type: %d", chaz_Make.shell_type);
     }
+
+    chaz_MakeRule_add_command(self, command);
+    free(command);
 }
 
 static void
 S_chaz_MakeBinary_destroy(chaz_MakeBinary *self) {
     size_t i;
 
-    free(self->target_dir);
-    free(self->basename);
-    free(self->version);
-    free(self->major_version);
-    free(self->dollar_var);
+    free(self->obj_dollar_var);
     S_chaz_MakeRule_destroy(self->rule);
 
     for (i = 0; i < self->num_sources; i++) {
@@ -5543,8 +5843,8 @@ S_chaz_MakeBinary_destroy(chaz_MakeBinary *self) {
     }
     free(self->dirs);
 
-    chaz_CFlags_destroy(self->compile_flags);
-    chaz_CFlags_destroy(self->link_flags);
+    chaz_CFlags_destroy(self->cflags);
+    chaz_CFlags_destroy(self->ldflags);
 
     free(self);
 }
@@ -5694,12 +5994,12 @@ chaz_MakeBinary_get_target(chaz_MakeBinary *self) {
 
 chaz_CFlags*
 chaz_MakeBinary_get_compile_flags(chaz_MakeBinary *self) {
-    return self->compile_flags;
+    return self->cflags;
 }
 
 chaz_CFlags*
 chaz_MakeBinary_get_link_flags(chaz_MakeBinary *self) {
-    return self->link_flags;
+    return self->ldflags;
 }
 
 void
@@ -6380,6 +6680,12 @@ chaz_Probe_parse_cli_args(int argc, const char *argv[], chaz_CLI *cli) {
     chaz_CLI_register(cli, "cc", "compiler command", CHAZ_CLI_ARG_REQUIRED);
     chaz_CLI_register(cli, "cflags", NULL, CHAZ_CLI_ARG_OPTIONAL);
     chaz_CLI_register(cli, "make", "make command", CHAZ_CLI_ARG_OPTIONAL);
+    chaz_CLI_register(cli, "prefix", "install prefix", CHAZ_CLI_ARG_OPTIONAL);
+    chaz_CLI_register(cli, "bindir", "install dir for executables", CHAZ_CLI_ARG_OPTIONAL);
+    chaz_CLI_register(cli, "datarootdir", "root install dir for data files", CHAZ_CLI_ARG_OPTIONAL);
+    chaz_CLI_register(cli, "datadir", "install dir for data files", CHAZ_CLI_ARG_OPTIONAL);
+    chaz_CLI_register(cli, "libdir", "install dir for libraries", CHAZ_CLI_ARG_OPTIONAL);
+    chaz_CLI_register(cli, "mandir", "install dir for man pages", CHAZ_CLI_ARG_OPTIONAL);
 
     /* Parse options, exiting on failure. */
     if (!chaz_CLI_parse(cli, argc, argv)) {
@@ -6468,7 +6774,7 @@ chaz_Probe_init(struct chaz_CLI *cli) {
     chaz_CC_init(chaz_CLI_strval(cli, "cc"), chaz_CLI_strval(cli, "cflags"));
     chaz_ConfWriter_init();
     chaz_HeadCheck_init();
-    chaz_Make_init(chaz_CLI_strval(cli, "make"));
+    chaz_Make_init(cli);
 
     /* Enable output. */
     if (chaz_CLI_defined(cli, "enable-c")) {
@@ -8482,6 +8788,9 @@ lucy_MakeFile_write_c_cfc_rules(lucy_MakeFile *self);
 static void
 lucy_MakeFile_write_c_test_rules(lucy_MakeFile *self);
 
+static void
+lucy_MakeFile_write_c_install_rules(lucy_MakeFile *self);
+
 static int
 S_core_dir_filter(const char *dir, char *file, void *context);
 
@@ -8716,6 +9025,8 @@ lucy_MakeFile_write(lucy_MakeFile *self) {
     const char *host     = chaz_CLI_strval(self->cli, "host");
     const char *math_lib = chaz_Floats_math_library();
 
+    int is_c = strcmp(host, "c") == 0;
+
     const char *lib_objs      = NULL;
     const char *test_lib_objs = NULL;
 
@@ -8762,14 +9073,15 @@ lucy_MakeFile_write(lucy_MakeFile *self) {
 
     /* Core library. */
 
-    if (strcmp(host, "c") == 0 || strcmp(host, "perl") == 0) {
+    if (is_c || strcmp(host, "perl") == 0) {
         /* Shared library for C and Perl. */
 
         chaz_MakeFile_add_rule(self->makefile, "all", "$(LUCY_SHARED_LIB)");
 
         self->lib
             = chaz_MakeFile_add_shared_lib(self->makefile, NULL, "lucy",
-                                           lucy_version, lucy_major_version);
+                                           lucy_version, lucy_major_version,
+                                           is_c);
         lib_objs = "$(LUCY_SHARED_LIB_OBJS)";
 
         compile_flags = chaz_MakeBinary_get_compile_flags(self->lib);
@@ -8799,7 +9111,7 @@ lucy_MakeFile_write(lucy_MakeFile *self) {
                                "$(LUCY_STATIC_LIB) $(TESTLUCY_STATIC_LIB)");
 
         self->lib
-            = chaz_MakeFile_add_static_lib(self->makefile, NULL, "lucy");
+            = chaz_MakeFile_add_static_lib(self->makefile, NULL, "lucy", 0);
         lib_objs = "$(LUCY_STATIC_LIB_OBJS)";
 
         compile_flags = chaz_MakeBinary_get_compile_flags(self->lib);
@@ -8828,12 +9140,13 @@ lucy_MakeFile_write(lucy_MakeFile *self) {
 
     /* Test library. */
 
-    if (strcmp(host, "c") == 0 || strcmp(host, "perl") == 0) {
+    if (is_c || strcmp(host, "perl") == 0) {
         /* Shared library for C and Perl. */
 
         self->test_lib
             = chaz_MakeFile_add_shared_lib(self->makefile, NULL, "testlucy",
-                                           lucy_version, lucy_major_version);
+                                           lucy_version, lucy_major_version,
+                                           0);
         test_lib_objs = "$(TESTLUCY_SHARED_LIB_OBJS)";
 
         compile_flags = chaz_MakeBinary_get_compile_flags(self->test_lib);
@@ -8860,8 +9173,8 @@ lucy_MakeFile_write(lucy_MakeFile *self) {
     else {
         /* Static library for Go and Python. */
 
-        self->test_lib
-            = chaz_MakeFile_add_static_lib(self->makefile, NULL, "testlucy");
+        self->test_lib = chaz_MakeFile_add_static_lib(self->makefile, NULL,
+                                                      "testlucy", 0);
         test_lib_objs = "$(TESTLUCY_STATIC_LIB_OBJS)";
 
         compile_flags = chaz_MakeBinary_get_compile_flags(self->test_lib);
@@ -8901,9 +9214,10 @@ lucy_MakeFile_write(lucy_MakeFile *self) {
     chaz_MakeFile_add_rule(self->makefile, test_lib_objs,
                            self->autogen_target);
 
-    if (strcmp(host, "c") == 0) {
+    if (is_c) {
         lucy_MakeFile_write_c_cfc_rules(self);
         lucy_MakeFile_write_c_test_rules(self);
+        lucy_MakeFile_write_c_install_rules(self);
     }
 
     /* Targets to compile object files for Perl. */
@@ -8989,7 +9303,7 @@ lucy_MakeFile_write_c_test_rules(lucy_MakeFile *self) {
     chaz_CFlags     *link_flags;
     chaz_MakeRule   *rule;
 
-    exe = chaz_MakeFile_add_exe(self->makefile, "t", "test_lucy");
+    exe = chaz_MakeFile_add_exe(self->makefile, "t", "test_lucy", 0);
     chaz_MakeBinary_add_src_file(exe, "t", "test_lucy.c");
 
     compile_flags = chaz_MakeBinary_get_compile_flags(exe);
@@ -9044,6 +9358,29 @@ lucy_MakeFile_write_c_test_rules(lucy_MakeFile *self) {
     }
 }
 
+static void
+lucy_MakeFile_write_c_install_rules(lucy_MakeFile *self) {
+    const char *dir_sep = chaz_OS_dir_sep();
+    char *src;
+
+    src = chaz_Util_join(dir_sep, "autogen", "share", NULL);
+    chaz_MakeFile_install_dir(self->makefile, src, "$(DATADIR)", NULL);
+    free(src);
+
+    src = chaz_Util_join(dir_sep, "autogen", "man", NULL);
+    chaz_MakeFile_install_dir(self->makefile, src, "$(MANDIR)", NULL);
+    free(src);
+
+    chaz_MakeFile_install_pkgconfig(self->makefile, "lucy", lucy_version,
+        "Name: Apache Lucy\n"
+        "Description: Full-text search for dynamic languages\n"
+        "URL: http://lucy.apache.org/\n"
+        "Version: $${version}\n"
+        "Requires: clownfish\n"
+        "Libs: -L$${libdir} -llucy\n"
+    );
+}
+
 static int
 S_core_dir_filter(const char *dir, char *file, void *context) {
     (void)dir;

http://git-wip-us.apache.org/repos/asf/lucy/blob/d6fcd5fb/common/charmonizer.main
----------------------------------------------------------------------
diff --git a/common/charmonizer.main b/common/charmonizer.main
index 013cc54..426d4eb 100644
--- a/common/charmonizer.main
+++ b/common/charmonizer.main
@@ -92,6 +92,9 @@ lucy_MakeFile_write_c_cfc_rules(lucy_MakeFile *self);
 static void
 lucy_MakeFile_write_c_test_rules(lucy_MakeFile *self);
 
+static void
+lucy_MakeFile_write_c_install_rules(lucy_MakeFile *self);
+
 static int
 S_core_dir_filter(const char *dir, char *file, void *context);
 
@@ -326,6 +329,8 @@ lucy_MakeFile_write(lucy_MakeFile *self) {
     const char *host     = chaz_CLI_strval(self->cli, "host");
     const char *math_lib = chaz_Floats_math_library();
 
+    int is_c = strcmp(host, "c") == 0;
+
     const char *lib_objs      = NULL;
     const char *test_lib_objs = NULL;
 
@@ -372,14 +377,15 @@ lucy_MakeFile_write(lucy_MakeFile *self) {
 
     /* Core library. */
 
-    if (strcmp(host, "c") == 0 || strcmp(host, "perl") == 0) {
+    if (is_c || strcmp(host, "perl") == 0) {
         /* Shared library for C and Perl. */
 
         chaz_MakeFile_add_rule(self->makefile, "all", "$(LUCY_SHARED_LIB)");
 
         self->lib
             = chaz_MakeFile_add_shared_lib(self->makefile, NULL, "lucy",
-                                           lucy_version, lucy_major_version);
+                                           lucy_version, lucy_major_version,
+                                           is_c);
         lib_objs = "$(LUCY_SHARED_LIB_OBJS)";
 
         compile_flags = chaz_MakeBinary_get_compile_flags(self->lib);
@@ -409,7 +415,7 @@ lucy_MakeFile_write(lucy_MakeFile *self) {
                                "$(LUCY_STATIC_LIB) $(TESTLUCY_STATIC_LIB)");
 
         self->lib
-            = chaz_MakeFile_add_static_lib(self->makefile, NULL, "lucy");
+            = chaz_MakeFile_add_static_lib(self->makefile, NULL, "lucy", 0);
         lib_objs = "$(LUCY_STATIC_LIB_OBJS)";
 
         compile_flags = chaz_MakeBinary_get_compile_flags(self->lib);
@@ -438,12 +444,13 @@ lucy_MakeFile_write(lucy_MakeFile *self) {
 
     /* Test library. */
 
-    if (strcmp(host, "c") == 0 || strcmp(host, "perl") == 0) {
+    if (is_c || strcmp(host, "perl") == 0) {
         /* Shared library for C and Perl. */
 
         self->test_lib
             = chaz_MakeFile_add_shared_lib(self->makefile, NULL, "testlucy",
-                                           lucy_version, lucy_major_version);
+                                           lucy_version, lucy_major_version,
+                                           0);
         test_lib_objs = "$(TESTLUCY_SHARED_LIB_OBJS)";
 
         compile_flags = chaz_MakeBinary_get_compile_flags(self->test_lib);
@@ -470,8 +477,8 @@ lucy_MakeFile_write(lucy_MakeFile *self) {
     else {
         /* Static library for Go and Python. */
 
-        self->test_lib
-            = chaz_MakeFile_add_static_lib(self->makefile, NULL, "testlucy");
+        self->test_lib = chaz_MakeFile_add_static_lib(self->makefile, NULL,
+                                                      "testlucy", 0);
         test_lib_objs = "$(TESTLUCY_STATIC_LIB_OBJS)";
 
         compile_flags = chaz_MakeBinary_get_compile_flags(self->test_lib);
@@ -511,9 +518,10 @@ lucy_MakeFile_write(lucy_MakeFile *self) {
     chaz_MakeFile_add_rule(self->makefile, test_lib_objs,
                            self->autogen_target);
 
-    if (strcmp(host, "c") == 0) {
+    if (is_c) {
         lucy_MakeFile_write_c_cfc_rules(self);
         lucy_MakeFile_write_c_test_rules(self);
+        lucy_MakeFile_write_c_install_rules(self);
     }
 
     /* Targets to compile object files for Perl. */
@@ -599,7 +607,7 @@ lucy_MakeFile_write_c_test_rules(lucy_MakeFile *self) {
     chaz_CFlags     *link_flags;
     chaz_MakeRule   *rule;
 
-    exe = chaz_MakeFile_add_exe(self->makefile, "t", "test_lucy");
+    exe = chaz_MakeFile_add_exe(self->makefile, "t", "test_lucy", 0);
     chaz_MakeBinary_add_src_file(exe, "t", "test_lucy.c");
 
     compile_flags = chaz_MakeBinary_get_compile_flags(exe);
@@ -654,6 +662,29 @@ lucy_MakeFile_write_c_test_rules(lucy_MakeFile *self) {
     }
 }
 
+static void
+lucy_MakeFile_write_c_install_rules(lucy_MakeFile *self) {
+    const char *dir_sep = chaz_OS_dir_sep();
+    char *src;
+
+    src = chaz_Util_join(dir_sep, "autogen", "share", NULL);
+    chaz_MakeFile_install_dir(self->makefile, src, "$(DATADIR)", NULL);
+    free(src);
+
+    src = chaz_Util_join(dir_sep, "autogen", "man", NULL);
+    chaz_MakeFile_install_dir(self->makefile, src, "$(MANDIR)", NULL);
+    free(src);
+
+    chaz_MakeFile_install_pkgconfig(self->makefile, "lucy", lucy_version,
+        "Name: Apache Lucy\n"
+        "Description: Full-text search for dynamic languages\n"
+        "URL: http://lucy.apache.org/\n"
+        "Version: $${version}\n"
+        "Requires: clownfish\n"
+        "Libs: -L$${libdir} -llucy\n"
+    );
+}
+
 static int
 S_core_dir_filter(const char *dir, char *file, void *context) {
     (void)dir;

http://git-wip-us.apache.org/repos/asf/lucy/blob/d6fcd5fb/devel/bin/appveyor-build.bat
----------------------------------------------------------------------
diff --git a/devel/bin/appveyor-build.bat b/devel/bin/appveyor-build.bat
index 5798496..4dcdaa0 100644
--- a/devel/bin/appveyor-build.bat
+++ b/devel/bin/appveyor-build.bat
@@ -49,9 +49,10 @@ call "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64
 
 :msvc_build
 
-cd lucy-clownfish\runtime\c
-call configure && nmake || exit /b
-call install --prefix C:\install
+cd lucy-clownfish\compiler\c
+call configure --prefix C:\install && nmake install || exit /b
+cd ..\..\runtime\c
+call configure --prefix C:\install && nmake install || exit /b
 
 cd ..\..\..\c
 call configure --clownfish-prefix C:\install && nmake && nmake test
@@ -63,13 +64,12 @@ exit /b
 path C:\MinGW\bin;%path%
 
 cd lucy-clownfish\compiler\c
-call configure && mingw32-make || exit /b
+call configure --prefix C:\install && mingw32-make install || exit /b
 cd ..\..\runtime\c
-call configure && mingw32-make || exit /b
-call install --prefix C:\install
+call configure --prefix C:\install && mingw32-make install || exit /b
 
 cd ..\..\..\c
-call configure --clownfish-prefix C:\install && mingw32-make && mingw32-make test
+call configure --clownfish-prefix C:\install && mingw32-make test
 
 exit /b
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/d6fcd5fb/devel/bin/test_all.sh
----------------------------------------------------------------------
diff --git a/devel/bin/test_all.sh b/devel/bin/test_all.sh
index 54fc496..a6e4a6e 100755
--- a/devel/bin/test_all.sh
+++ b/devel/bin/test_all.sh
@@ -107,18 +107,20 @@ fi
 
 if [ -z "$1" -o "$1" = c ]; then
     cd "$cfish_dir/compiler/c"
-    ./configure
+    ./configure --prefix="$tmp_dir/c"
     make -j test
+    make install
 
     cd ../../runtime/c
-    ./configure
+    ./configure --prefix="$tmp_dir/c"
     make -j test
-    ./install.sh --prefix "$tmp_dir/c"
+    make install
     make distclean
 
     cd "$lucy_dir/c"
-    ./configure --clownfish-prefix "$tmp_dir/c"
+    ./configure --prefix="$tmp_dir/c" --clownfish-prefix "$tmp_dir/c"
     make -j test
+    make install
     make distclean
 
     cd "$root"

http://git-wip-us.apache.org/repos/asf/lucy/blob/d6fcd5fb/devel/bin/travis-test.sh
----------------------------------------------------------------------
diff --git a/devel/bin/travis-test.sh b/devel/bin/travis-test.sh
index 4e07247..bb7ce13 100755
--- a/devel/bin/travis-test.sh
+++ b/devel/bin/travis-test.sh
@@ -29,10 +29,12 @@ git clone -q --depth 1 https://git-wip-us.apache.org/repos/asf/lucy-clownfish.gi
 
 test_c() {
     # Install Clownfish.
-    cd lucy-clownfish/runtime/c
-    ./configure
-    make -j
-    ./install.sh --prefix "$install_dir"
+    cd lucy-clownfish/compiler/c
+    ./configure --prefix="$install_dir"
+    make -j install
+    cd ../../runtime/c
+    ./configure --prefix="$install_dir"
+    make -j install
 
     # Needed to find DLL on Windows.
     export PATH="$install_dir/bin:$PATH"

http://git-wip-us.apache.org/repos/asf/lucy/blob/d6fcd5fb/devel/bin/update_version
----------------------------------------------------------------------
diff --git a/devel/bin/update_version b/devel/bin/update_version
index 35c8dd0..9795390 100755
--- a/devel/bin/update_version
+++ b/devel/bin/update_version
@@ -164,14 +164,6 @@ find sub {
     write_file($name, $buf);
 }, 'perl';
 
-# Update c/install.sh
-$buf = read_file('c/install.sh');
-$buf =~ s/\bversion=[\d.]+/version=$x_y_z_version/
-    or die "no match";
-$buf =~ s/\bmajor_version=[\d.]+/major_version=$x.$y/
-    or die "no match";
-write_file( 'c/install.sh', $buf );
-
 # utility functions
 sub read_file {
     my ($file) = @_;