mirror of
https://github.com/Z3Prover/z3
synced 2025-04-06 01:24:08 +00:00
Add bootstrap.py script to copy CMake files into their correct location
on a user's machine and add documentation for this. Also add a ``maintainers.txt`` file.
This commit is contained in:
parent
a3e0eae9ec
commit
875acfc210
147
README-CMake.md
147
README-CMake.md
|
@ -10,39 +10,56 @@ on your platform. Example generators include "UNIX Makfiles" and "Visual Studio
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
The general workflow when using CMake is the following
|
### Fixing a polluted source tree
|
||||||
|
|
||||||
1. Create a new build directory
|
If you have never used the python build system you can skip this step.
|
||||||
2. Configure the project
|
|
||||||
3. Generate the build system
|
|
||||||
4. Invoke the build system to build the project
|
|
||||||
|
|
||||||
To perform steps 2 and 3 you can choose from three different tools
|
The existing Python build system creates generated source files in
|
||||||
|
the build tree. The CMake build system will refuse to work if it
|
||||||
|
detects this so you need to clean your build tree first.
|
||||||
|
|
||||||
* cmake
|
To do this run the following in the root of the repository
|
||||||
* ccmake
|
|
||||||
* cmake-gui
|
|
||||||
|
|
||||||
``cmake`` is a command line tool and is what you should use if you are
|
```
|
||||||
writing a script to build Z3. This tool performs steps 1 and 2 in one
|
git clean -nx src
|
||||||
go without user interaction. The ``ccmake`` and ``cmake-gui`` tools are
|
```
|
||||||
more interactive and allow you to change various options. In both these
|
|
||||||
tools the basic steps to follow are:
|
|
||||||
|
|
||||||
1. Configure.
|
This will list everything that will be removed. If you are happy
|
||||||
2. Change any options you wish. Everytime you change a set of options
|
with this then run.
|
||||||
You should configure again. This may cause new options to appear
|
|
||||||
3. Generate.
|
|
||||||
|
|
||||||
For information see https://cmake.org/runningcmake/
|
```
|
||||||
|
git clean -fx src
|
||||||
|
```
|
||||||
|
|
||||||
Note when invoking CMake you give it the path to the source directory.
|
which will remove the generated source files.
|
||||||
This is the top-level directory in the Z3 repository containing a
|
|
||||||
``CMakeLists.txt``. That file should contain the line ``project(Z3 C CXX)``.
|
|
||||||
If you give it a path deeper into the Z3 repository (e.g. the ``src`` directory)
|
|
||||||
the configure step will fail.
|
|
||||||
|
|
||||||
What follows is a brief walk through of how to build Z3 using various generators.
|
### Bootstrapping
|
||||||
|
|
||||||
|
Most of Z3's CMake files do not live in their correct location. Instead those
|
||||||
|
files live in the ``contrib/cmake`` folder and a script is provided that will
|
||||||
|
copy (or hard link) the files into their correct location.
|
||||||
|
|
||||||
|
To copy the files run
|
||||||
|
|
||||||
|
```
|
||||||
|
python contrib/cmake/bootstrap.py create
|
||||||
|
```
|
||||||
|
|
||||||
|
in the root of the repository. Once you have done this you can now build Z3 using CMake.
|
||||||
|
Remember to rerun this command if you pull down new code or change branch so
|
||||||
|
that the CMake files are up to date.
|
||||||
|
|
||||||
|
To remove the copied files run
|
||||||
|
|
||||||
|
```
|
||||||
|
python contrib/cmake/bootstrap.py remove
|
||||||
|
```
|
||||||
|
|
||||||
|
Note if you plan to do development on Z3 you should read the developer
|
||||||
|
notes on bootstrapping in this document.
|
||||||
|
|
||||||
|
What follows is a brief walk through of how to build Z3 using some
|
||||||
|
of the more commonly used CMake generators.
|
||||||
|
|
||||||
### Unix Makefiles
|
### Unix Makefiles
|
||||||
|
|
||||||
|
@ -52,7 +69,7 @@ Run the following in the top level directory of the Z3 repository.
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake -G "Unix Makefiles" ../
|
cmake -G "Unix Makefiles" ../
|
||||||
make -j4
|
make -j4 # Replace 4 with an appropriate number
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that on some platforms "Unix Makesfiles" is the default generator so on those
|
Note that on some platforms "Unix Makesfiles" is the default generator so on those
|
||||||
|
@ -131,7 +148,7 @@ environment where the compiler can be found. If you have Visual Studio
|
||||||
installed it typically ships with a "Developer Command Prompt Window" that you
|
installed it typically ships with a "Developer Command Prompt Window" that you
|
||||||
can use which has the environment variables setup for you.
|
can use which has the environment variables setup for you.
|
||||||
|
|
||||||
## NMake
|
### NMake
|
||||||
|
|
||||||
NMake is a build system that ships with Visual Studio. You are advised to use
|
NMake is a build system that ships with Visual Studio. You are advised to use
|
||||||
Ninja instead which is significantly faster due to supporting concurrent
|
Ninja instead which is significantly faster due to supporting concurrent
|
||||||
|
@ -189,6 +206,40 @@ are multi-configuration generators which means you don't set the build type when
|
||||||
CMake. Instead you set the build type inside Visual Studio. See the "Build Type" section
|
CMake. Instead you set the build type inside Visual Studio. See the "Build Type" section
|
||||||
for more information.
|
for more information.
|
||||||
|
|
||||||
|
### General workflow
|
||||||
|
|
||||||
|
The general workflow when using CMake is the following
|
||||||
|
|
||||||
|
1. Create a new build directory
|
||||||
|
2. Configure the project
|
||||||
|
3. Generate the build system
|
||||||
|
4. Invoke the build system to build the project
|
||||||
|
|
||||||
|
To perform steps 2 and 3 you can choose from three different tools
|
||||||
|
|
||||||
|
* cmake
|
||||||
|
* ccmake
|
||||||
|
* cmake-gui
|
||||||
|
|
||||||
|
``cmake`` is a command line tool and is what you should use if you are
|
||||||
|
writing a script to build Z3. This tool performs steps 1 and 2 in one
|
||||||
|
go without user interaction. The ``ccmake`` and ``cmake-gui`` tools are
|
||||||
|
more interactive and allow you to change various options. In both these
|
||||||
|
tools the basic steps to follow are:
|
||||||
|
|
||||||
|
1. Configure.
|
||||||
|
2. Change any options you wish. Everytime you change a set of options
|
||||||
|
You should configure again. This may cause new options to appear
|
||||||
|
3. Generate.
|
||||||
|
|
||||||
|
For information see https://cmake.org/runningcmake/
|
||||||
|
|
||||||
|
Note when invoking CMake you give it the path to the source directory.
|
||||||
|
This is the top-level directory in the Z3 repository containing a
|
||||||
|
``CMakeLists.txt``. That file should contain the line ``project(Z3 C CXX)``.
|
||||||
|
If you give it a path deeper into the Z3 repository (e.g. the ``src`` directory)
|
||||||
|
the configure step will fail.
|
||||||
|
|
||||||
## Build Types
|
## Build Types
|
||||||
|
|
||||||
Several build types are supported.
|
Several build types are supported.
|
||||||
|
@ -231,18 +282,56 @@ cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_TRACING=FALSE ../
|
||||||
|
|
||||||
These notes are help developers and packagers of Z3.
|
These notes are help developers and packagers of Z3.
|
||||||
|
|
||||||
|
### Boostrapping the CMake build
|
||||||
|
|
||||||
|
Z3's CMake system is experimental and not officially supported. Consequently
|
||||||
|
Z3's developers have decided that they do not want the CMake files in the
|
||||||
|
``src/`` and ``examples/`` folders. Instead the ``contrib/cmake/bootstrap.py``
|
||||||
|
script copies or hard links them into the correct locations. For context
|
||||||
|
on this decision see https://github.com/Z3Prover/z3/pull/461 .
|
||||||
|
|
||||||
|
The ``contrib/cmake/bootstrap.py create`` command just copies over files which makes
|
||||||
|
development harder because you have to copy your modifications over to the
|
||||||
|
files in ``contrib/cmake`` for your changes to committed to git. If you are on a UNIX like
|
||||||
|
platform you can create hard links instead by running
|
||||||
|
|
||||||
|
```
|
||||||
|
contrib/cmake/boostrap.py create --hard-link
|
||||||
|
```
|
||||||
|
|
||||||
|
Using hard links means that modifying any of the "copied" files also modifies the
|
||||||
|
file under version control. Using hard links also means that the file modification time
|
||||||
|
will appear correct (i.e. the hard-linked "copies" have the same file modification time
|
||||||
|
as the corresponding file under version control) which means CMake will correctly reconfigure
|
||||||
|
when invoked. This is why using symbolic links is not an option because the file modification
|
||||||
|
time of a symbolic link is not the same as the file modification of the file being pointed to.
|
||||||
|
|
||||||
|
Unfortunately a downside to using hard links (or just plain copies) is that if
|
||||||
|
you pull down new code (i.e. ``git pull``) then some of the CMake files under
|
||||||
|
version control may change but the corresponding hard-linked "copies" will not.
|
||||||
|
|
||||||
|
This mean that (regardless of whether or not you use hard links) every time you
|
||||||
|
pull down new code or change branch or do an interactive rebase you must run
|
||||||
|
(with or without ``--hard-link``):
|
||||||
|
|
||||||
|
```
|
||||||
|
contrb/cmake/boostrap.py create
|
||||||
|
```
|
||||||
|
|
||||||
|
in order to be sure that the copied CMake files are not out of date.
|
||||||
|
|
||||||
### Install/Uninstall
|
### Install/Uninstall
|
||||||
|
|
||||||
Install and uninstall targets are supported. Use ``CMAKE_INSTALL_PREFIX`` to set the install
|
Install and uninstall targets are supported. Use ``CMAKE_INSTALL_PREFIX`` to set the install
|
||||||
prefix.
|
prefix.
|
||||||
|
|
||||||
To install
|
To install run
|
||||||
|
|
||||||
```
|
```
|
||||||
make install
|
make install
|
||||||
```
|
```
|
||||||
|
|
||||||
To uninstall
|
To uninstall run
|
||||||
|
|
||||||
```
|
```
|
||||||
make uninstall
|
make uninstall
|
||||||
|
|
254
contrib/cmake/bootstrap.py
Executable file
254
contrib/cmake/bootstrap.py
Executable file
|
@ -0,0 +1,254 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
This script is used to copy or delete the
|
||||||
|
CMake build system files from the contrib/cmake
|
||||||
|
folder into the their correct location in the Z3
|
||||||
|
repository.
|
||||||
|
|
||||||
|
It offers two modes
|
||||||
|
|
||||||
|
* create - This will symlink the ``cmake`` directory and copy (or hard link)
|
||||||
|
the appropriate files into their correct locations in the repository.
|
||||||
|
|
||||||
|
* remove - This will remove the symlinked ``cmake``
|
||||||
|
directory and remove the files added by the above
|
||||||
|
methods.
|
||||||
|
|
||||||
|
This has the advantage
|
||||||
|
that editing the hard link edits the underlying file
|
||||||
|
(making development easier because copying files is
|
||||||
|
not neccessary) and CMake will regenerate correctly
|
||||||
|
because the modification time stamps will be correct.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pprint
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def get_full_path_to_script():
|
||||||
|
return os.path.abspath(__file__)
|
||||||
|
|
||||||
|
def get_cmake_contrib_dir():
|
||||||
|
return os.path.dirname(get_full_path_to_script())
|
||||||
|
|
||||||
|
def get_repo_root_dir():
|
||||||
|
r = os.path.dirname(os.path.dirname(get_cmake_contrib_dir()))
|
||||||
|
assert os.path.isdir(r)
|
||||||
|
return r
|
||||||
|
|
||||||
|
# These are paths that should be ignored when checking if a folder
|
||||||
|
# in the ``contrib/cmake`` exists in the root of the repository
|
||||||
|
verificationExceptions = {
|
||||||
|
os.path.join(get_repo_root_dir(), 'cmake'),
|
||||||
|
os.path.join(get_repo_root_dir(), 'cmake', 'modules')
|
||||||
|
}
|
||||||
|
|
||||||
|
def contribPathToRepoPath(path):
|
||||||
|
assert path.startswith(get_cmake_contrib_dir())
|
||||||
|
stripped = path[len(get_cmake_contrib_dir()) + 1:] # Plus one is to remove leading slash
|
||||||
|
assert not os.path.isabs(stripped)
|
||||||
|
logging.debug('stripped:{}'.format(stripped))
|
||||||
|
r = os.path.join(get_repo_root_dir(), stripped)
|
||||||
|
assert os.path.isabs(r)
|
||||||
|
logging.debug('Converted contrib path "{}" to repo path "{}"'.format(path, r))
|
||||||
|
return r
|
||||||
|
|
||||||
|
def verify_mirrored_directory_struture():
|
||||||
|
"""
|
||||||
|
Check that the directories contained in ``contrib/cmake`` exist
|
||||||
|
in the root of the repo.
|
||||||
|
"""
|
||||||
|
for (dirpath, _, _) in os.walk(get_cmake_contrib_dir()):
|
||||||
|
expectedDir = contribPathToRepoPath(dirpath)
|
||||||
|
logging.debug('expectedDir:{}'.format(expectedDir))
|
||||||
|
if (not (os.path.exists(expectedDir) and os.path.isdir(expectedDir)) and
|
||||||
|
expectedDir not in verificationExceptions):
|
||||||
|
logging.error(('Expected to find directory "{}" but it does not exist'
|
||||||
|
' or is not a directory').format(expectedDir))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def mk_sym_link(target, linkName):
|
||||||
|
logging.info('Making symbolic link target="{}", linkName="{}"'.format(target, linkName))
|
||||||
|
if os.path.exists(linkName):
|
||||||
|
logging.info('Removing existing link "{}"'.format(linkName))
|
||||||
|
if not os.path.islink(linkName):
|
||||||
|
logging.warning('"{}" overwriting file that is not a symlink'.format(linkName))
|
||||||
|
delete_path(linkName)
|
||||||
|
if os.name == 'posix':
|
||||||
|
os.symlink(src=target, dst=linkName)
|
||||||
|
else:
|
||||||
|
# TODO: Windows does support symlinks but the implementation to do that
|
||||||
|
# from python is a little complicated so for now lets just copy everyting
|
||||||
|
logging.warning('Creating symbolic links is not supported. Just making a copy instead')
|
||||||
|
if os.path.isdir(target):
|
||||||
|
# Recursively copy directory
|
||||||
|
shutil.copytree(src=target, dst=linkName, symlinks=False, ignore=False)
|
||||||
|
else:
|
||||||
|
# Copy file
|
||||||
|
assert os.path.isfile(target)
|
||||||
|
shutil.copy2(src=target, dst=linkName)
|
||||||
|
|
||||||
|
def delete_path(path):
|
||||||
|
logging.info('Removing "{}"'.format(path))
|
||||||
|
if not os.path.exists(path):
|
||||||
|
logging.warning('"{}" does not exist'.format(path))
|
||||||
|
return
|
||||||
|
if os.path.isdir(path) and not os.path.islink(path):
|
||||||
|
# FIXME: If we can get symbolic link support on Windows we
|
||||||
|
# can disallow this completely.
|
||||||
|
assert os.name == 'nt'
|
||||||
|
shutil.rmtree(path)
|
||||||
|
else:
|
||||||
|
os.remove(path)
|
||||||
|
|
||||||
|
def shouldSkipFile(path):
|
||||||
|
# Skip this script
|
||||||
|
if path == get_full_path_to_script():
|
||||||
|
return True
|
||||||
|
# Skip the maintainers file
|
||||||
|
if path == os.path.join(get_cmake_contrib_dir(), 'maintainers.txt'):
|
||||||
|
return True
|
||||||
|
# Skip Vim temporary files
|
||||||
|
if os.path.basename(path).startswith('.') and path.endswith('.swp'):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def create(useHardLinks):
|
||||||
|
"""
|
||||||
|
Copy or hard link files in the CMake contrib directory
|
||||||
|
into the repository where they are intended to live.
|
||||||
|
|
||||||
|
Note that symbolic links for the CMakeLists.txt files
|
||||||
|
are not appropriate because they won't have the right
|
||||||
|
file modification time when the files they point to
|
||||||
|
are modified. This would prevent CMake from correctly
|
||||||
|
reconfiguring when it detects this is required.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Make the ``cmake`` directory a symbolic link.
|
||||||
|
# We treat this one specially as it is the only directory
|
||||||
|
# that doesn't already exist in the repository root so
|
||||||
|
# we can just use a symlink here
|
||||||
|
linkName = os.path.join(get_repo_root_dir(), 'cmake')
|
||||||
|
target = os.path.join(get_cmake_contrib_dir(), 'cmake')
|
||||||
|
specialDir = target
|
||||||
|
mk_sym_link(target, linkName)
|
||||||
|
|
||||||
|
for (dirPath,_ , fileNames) in os.walk(get_cmake_contrib_dir()):
|
||||||
|
# Skip the special directory and its children
|
||||||
|
if dirPath.startswith(specialDir):
|
||||||
|
logging.info('Skipping directory "{}"'.format(dirPath))
|
||||||
|
continue
|
||||||
|
|
||||||
|
for fileName in fileNames:
|
||||||
|
fileInContrib = os.path.join(dirPath, fileName)
|
||||||
|
# Skip files
|
||||||
|
if shouldSkipFile(fileInContrib):
|
||||||
|
logging.info('Skipping "{}"'.format(fileInContrib))
|
||||||
|
continue
|
||||||
|
fileInRepo = contribPathToRepoPath(fileInContrib)
|
||||||
|
logging.info('"{}" => "{}"'.format(fileInContrib, fileInRepo))
|
||||||
|
if useHardLinks:
|
||||||
|
if not os.name == 'posix':
|
||||||
|
logging.error('Hard links are not supported on your platform')
|
||||||
|
return False
|
||||||
|
if os.path.exists(fileInRepo):
|
||||||
|
delete_path(fileInRepo)
|
||||||
|
os.link(src=fileInContrib, dst=fileInRepo)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
shutil.copy2(src=fileInContrib, dst=fileInRepo)
|
||||||
|
except shutil.SameFileError as e:
|
||||||
|
# Can hit this if used created hard links first and then run again without
|
||||||
|
# wanting hard links
|
||||||
|
logging.error('Trying to copy "{}" to "{}" but they are the same file'.format(
|
||||||
|
fileInContrib, fileInRepo))
|
||||||
|
logging.error('You should remove the files using the "remove" mode '
|
||||||
|
'and try to create again. You probably are mixing the '
|
||||||
|
'hard-link and non-hard-link create modes')
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def remove():
|
||||||
|
"""
|
||||||
|
Remove the CMake files from their intended location in
|
||||||
|
the repository. This is used to remove
|
||||||
|
the files created by the ``create()`` function.
|
||||||
|
"""
|
||||||
|
# This directory is treated specially as it is normally
|
||||||
|
# a symlink.
|
||||||
|
linkName = os.path.join(get_repo_root_dir(), 'cmake')
|
||||||
|
delete_path(linkName)
|
||||||
|
specialDir = os.path.join(get_cmake_contrib_dir(), 'cmake')
|
||||||
|
|
||||||
|
for (dirPath,_ , fileNames) in os.walk(get_cmake_contrib_dir()):
|
||||||
|
# Skip the special directory and its children
|
||||||
|
if dirPath.startswith(specialDir):
|
||||||
|
logging.info('Skipping directory "{}"'.format(dirPath))
|
||||||
|
continue
|
||||||
|
for fileName in fileNames:
|
||||||
|
fileInContrib = os.path.join(dirPath, fileName)
|
||||||
|
# Skip files
|
||||||
|
if shouldSkipFile(fileInContrib):
|
||||||
|
logging.info('Skipping "{}"'.format(fileInContrib))
|
||||||
|
continue
|
||||||
|
fileInRepo = contribPathToRepoPath(fileInContrib)
|
||||||
|
if os.path.exists(fileInRepo):
|
||||||
|
logging.info('Removing "{}"'.format(fileInRepo))
|
||||||
|
delete_path(fileInRepo)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument('mode',
|
||||||
|
choices=['create', 'remove'],
|
||||||
|
help='The mode to use')
|
||||||
|
parser.add_argument("-l","--log-level",
|
||||||
|
type=str,
|
||||||
|
default="info",
|
||||||
|
dest="log_level",
|
||||||
|
choices=['debug','info','warning','error']
|
||||||
|
)
|
||||||
|
parser.add_argument("-H", "--hard-link",
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
dest='hard_link',
|
||||||
|
help='When using the create mode create hard links instead of copies'
|
||||||
|
)
|
||||||
|
pargs = parser.parse_args(args)
|
||||||
|
|
||||||
|
logLevel = getattr(logging, pargs.log_level.upper(),None)
|
||||||
|
logging.basicConfig(level=logLevel)
|
||||||
|
|
||||||
|
# Before we start make sure we can transplant the CMake files on to
|
||||||
|
# repository
|
||||||
|
if verify_mirrored_directory_struture() != 0:
|
||||||
|
logging.error('"{}" does not mirror "{}"'.format(get_cmake_contrib_dir(), get_repo_root_dir()))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if pargs.mode == "create":
|
||||||
|
if not create(useHardLinks=pargs.hard_link):
|
||||||
|
logging.error("Failed to create")
|
||||||
|
return 1
|
||||||
|
elif pargs.mode == "create_hard_link":
|
||||||
|
if not create(useHardLinks=True):
|
||||||
|
logging.error("Failed to create_hard_link")
|
||||||
|
return 1
|
||||||
|
elif pargs.mode == "remove":
|
||||||
|
if not remove():
|
||||||
|
logging.error("Failed to remove")
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
logging.error('Unknown mode "{}"'.format(pargs.mode))
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
3
contrib/cmake/maintainers.txt
Normal file
3
contrib/cmake/maintainers.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Maintainers
|
||||||
|
|
||||||
|
- Dan Liew (@delcypher)
|
Loading…
Reference in a new issue