1 Commits

Author SHA1 Message Date
Ralph Lange
ecb7e43660 travis: fix for EXTRA with quotes/spaces 2020-04-23 17:43:21 +02:00
30 changed files with 1860 additions and 3061 deletions

View File

@@ -15,6 +15,14 @@
cache:
- C:\Users\appveyor\.tools -> appveyor\do.py
#---------------------------------#
# additional packages #
#---------------------------------#
install:
# for the sequencer
- cinst re2c
#---------------------------------#
# repository cloning #
#---------------------------------#
@@ -23,8 +31,6 @@ cache:
init:
# Set autocrlf to make batch files work
- git config --global core.autocrlf true
# print the connection info for RDP connections (see 'debugging' below)
- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
# Set clone depth (do not fetch complete history)
clone_depth: 50
@@ -36,31 +42,17 @@ skip_commits:
- 'templates/*'
- '**/*.html'
- '**/*.md'
- '.travis.yml'
- '.github/workflows/*'
#---------------------------------#
# additional packages #
#---------------------------------#
install:
# fetch submodules (like ci-scripts)
- cmd: git submodule update --init --recursive
# for the sequencer
- cinst re2c
#---------------------------------#
# build matrix configuration #
#---------------------------------#
# Default build worker image
image: Visual Studio 2015
# Build Configurations: shared/static, optimized/debug
# Build Configurations: dll/static, regular/debug
configuration:
- default
- dynamic
- static
- debug
- dynamic-debug
- static-debug
# Environment variables: compiler toolchain, base version, setup file, ...
@@ -73,24 +65,20 @@ environment:
matrix:
- CMP: vs2019
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
SET: test00
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
- CMP: gcc
- CMP: mingw
- CMP: vs2019
VV: 0
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
VV: 0
- CMP: vs2019
BASE: 3.15
CLEAN_DEPS: NO
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
- CMP: vs2019
BASE: 3.14
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
- CMP: vs2017
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
EXTRA: EXTRA_DEF="extra"
EXTRA1: EXTRA1_DEF="extra 1"
EXTRA2: EXTRA2_DEF="extra 2"
- CMP: vs2015
- CMP: vs2013
- CMP: vs2012
@@ -102,21 +90,15 @@ platform:
- x86
- x64
# Matrix configuration: allow/exclude specific failing jobs
# Matrix configuration: allow specific failing jobs
matrix:
# The MSI tool generated from that builds segfaults (*shrug*)
allow_failures:
- platform: x64
configuration: static-debug
CMP: vs2019
BASE: 3.14
exclude:
# Run test00 only once: x64 dynamic
- platform: x86
SET: test00
- configuration: static
SET: test00
- configuration: debug
- configuration: dynamic-debug
SET: test00
- configuration: static-debug
SET: test00
@@ -135,7 +117,7 @@ for:
only:
- SET: test00
build_script:
- cmd: python cue-test.py
- cmd: python appveyor-test.py
test_script:
- cmd: echo Tests have been run in the build phase
@@ -144,12 +126,11 @@ for:
#---------------------------------#
build_script:
- cmd: python cue.py prepare
- cmd: python cue.py build
- cmd: python appveyor/do.py prepare
- cmd: python appveyor/do.py build
test_script:
- cmd: python cue.py test
- cmd: python cue.py test-results
- cmd: python appveyor/do.py test
#---------------------------------#
# debugging #
@@ -160,6 +141,7 @@ test_script:
## so you may want to adjust the build matrix above to just build the one of interest
#on_failure:
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))

View File

@@ -1,154 +0,0 @@
name: ci-scripts build/test
on: [push, pull_request]
env:
SETUP_PATH: .:.ci
SET: test01
VV: 1
BASE_RECURSIVE: NO
CMP: gcc
APT: re2c
CHOCO: re2c
BREW: re2c
jobs:
unit-test:
name: Unit tests on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, ubuntu-18.04, ubuntu-16.04, windows-2019, windows-2016, macos-10.15]
steps:
- uses: actions/checkout@v2
- name: Show initial environment
run: python cue-test.py env
- name: Run unit tests
run: python cue-test.py
build-linux:
name: ${{ matrix.cmp }} / ${{ matrix.configuration }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, ubuntu-18.04, ubuntu-16.04]
cmp: [gcc, clang]
configuration: [default, static, debug, static-debug]
steps:
- uses: actions/checkout@v2
- name: Prepare and compile dependencies
run: python cue.py prepare
- name: Build main module (example app)
run: python cue.py build
- name: Run main module tests
run: python cue.py test
- name: Collect and show test results
run: python cue.py test-results
build-macos:
name: ${{ matrix.cmp }} / ${{ matrix.configuration }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
strategy:
fail-fast: false
matrix:
os: [macos-10.15]
cmp: [clang]
configuration: [default, debug]
steps:
- uses: actions/checkout@v2
- name: Prepare and compile dependencies
run: python cue.py prepare
- name: Build main module (example app)
run: python cue.py build
- name: Run main module tests
run: python cue.py test
- name: Collect and show test results
run: python cue.py test-results
build-windows:
name: ${{ matrix.cmp }} / ${{ matrix.configuration }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
strategy:
fail-fast: false
matrix:
os: [windows-2019, windows-2016]
cmp: [gcc, vs2019, vs2017]
configuration: [default, static, debug, static-debug]
exclude:
- os: windows-2019
cmp: vs2017
- os: windows-2016
cmp: vs2019
steps:
- uses: actions/checkout@v2
- name: Prepare and compile dependencies
run: python cue.py prepare
- name: Build main module (example app)
run: python cue.py build
- name: Run main module tests
run: python cue.py test
- name: Collect and show test results
run: python cue.py test-results
build-rtems:
name: RTEMS${{ matrix.rtems }} / ${{ matrix.configuration }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
RTEMS: ${{ matrix.rtems }}
APT: re2c g++-mingw-w64-i686 g++-mingw-w64-x86-64 qemu-system-x86
strategy:
fail-fast: false
matrix:
os: [ubuntu-18.04]
cmp: [gcc]
configuration: [default, static, debug, static-debug]
rtems: ["4.9", "4.10"]
steps:
- uses: actions/checkout@v2
- name: Prepare and compile dependencies
run: python cue.py prepare
- name: Build main module (example app)
run: python cue.py build
- name: Run main module tests
run: python cue.py test
- name: Collect and show test results
run: python cue.py test-results
build-wine:
name: WINE${{ matrix.wine }} / ${{ matrix.configuration }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
WINE: ${{ matrix.wine }}
APT: re2c g++-mingw-w64-i686 g++-mingw-w64-x86-64
strategy:
fail-fast: false
matrix:
os: [ubuntu-18.04]
cmp: [gcc]
configuration: [default, static, debug, static-debug]
wine: [32, 64]
steps:
- uses: actions/checkout@v2
- name: Prepare and compile dependencies
run: python cue.py prepare
- name: Build main module (example app)
run: python cue.py build
- name: Run main module tests
run: python cue.py test
- name: Collect and show test results
run: python cue.py test-results

View File

@@ -16,9 +16,6 @@ cache:
env:
global:
- SETUP_PATH=.:.ci
- VV=1
- CHOCO=re2c
- BASE_RECURSIVE=NO
addons:
apt:
@@ -30,20 +27,17 @@ addons:
- g++-mingw-w64-i686
- g++-mingw-w64-x86-64
- qemu-system-x86
- p7zip-full
homebrew:
packages:
- re2c
- p7zip
- bash
update: true
install:
- python cue.py prepare
- ./travis/prepare.sh
script:
- python cue.py build
- python cue.py test
- python cue.py test-results
- ./travis-test.sh
# If you need to do more during install and build,
# add a local directory to your module and do e.g.
@@ -52,125 +46,136 @@ script:
# Define build jobs
# Well-known variables to use
# SET source setup file
# ADD_MODULES extra modules (for a specific job)
# EXTRA content will be added to make command line
# BCFG build configuration (static/debug/static-debug;
# default: shared-optimized)
# TEST set to NO to skip running the tests (default: YES)
# VV set to make build scripts verbose (default: unset)
# SET source setup file
# EXTRA content will be added to make command line
# STATIC set to YES for static build (default: NO)
# TEST set to NO to skip running the tests (default: YES)
# VV set to make build scripts verbose (default: unset)
# Usually from setup files, but may be specified or overridden
# on a job line
# MODULES list of dependency modules
# BASE branch or release tag name of the EPICS Base to use
# <MODULE> branch or release tag for a specific module
# ... see README for setup file syntax description
# MODULES list of dependency modules
# BASE branch or release tag name of the EPICS Base to use
# <MODULE> branch or release tag for a specific module
# ... see README for setup file syntax description
jobs:
include:
# Run unit tests
# Run unit tests on Linux and Mac
- env: SET=test00
install: python cue-test.py env
script: python cue-test.py
- env: SET=test00 TRAVIS_COMPILER=gcc
language: python
python: "3.7"
install: python cue-test.py env
script: python cue-test.py
- env: SET=test00
os: osx
install: python cue-test.py env
script: python cue-test.py
- env: SET=test00
os: windows
install: python cue-test.py env
script: python cue-test.py
# Compile example
# Compile the example application
# using the build configurations from full makeBaseApp example
- env: SET=test01
dist: bionic
- env: SET=test01 BCFG=static-debug
dist: bionic
# On the side: test ADD_MODULES
- env: SET=test01 ADD_MODULES=ipac
compiler: clang
- env: SET=test01 CLEAN_DEPS=NO
dist: trusty
- env: SET=test01 BCFG=static-debug
dist: trusty
- env: SET=test01 TRAVIS_COMPILER=gcc
language: python
python: "3.7"
- env: VV="" SET=test01
- env: SET=test01 EXTRA="CMD_CXXFLAGS=-std=c++11"
- env: SET=test01 BCFG=static
- env: SET=test01 BCFG=debug
- env: SET=test01 BCFG=static-debug
- env: SET=test01 EXTRA="CMD_CXXFLAGS=-std=c++11"
compiler: clang
# trusty is pretty close to RHEL7
- env: SET=test01
dist: trusty
- env: SET=test01 EXTRA="CMD_CXXFLAGS=-std=c++11"
dist: trusty
# Cross-compilation to Windows using MinGW and WINE
- env: SET=test01 WINE=32 TEST=NO STATIC=YES
compiler: mingw
- env: SET=test01 WINE=64 TEST=NO STATIC=YES
compiler: mingw
# dynamic (DLL) builds are broken on xenial
- env: SET=test01 WINE=32 TEST=NO STATIC=NO
dist: bionic
compiler: mingw
- env: SET=test01 WINE=64 TEST=NO STATIC=NO
dist: bionic
compiler: mingw
# Cross-compilation to RTEMS
# (needs EPICS Base >= 3.16.2)
- env: SET=test01 RTEMS=4.10
- env: SET=test01 RTEMS=4.9
# Other gcc versions (adding as an extra package)
- env: SET=test01
compiler: clang
compiler: gcc-6
addons: { apt: { packages: ["g++-6"], sources: ["ubuntu-toolchain-r-test"] } }
- env: SET=test01 BCFG=static-debug
compiler: clang
- env: SET=test01
compiler: gcc-7
addons: { apt: { packages: ["g++-7"], sources: ["ubuntu-toolchain-r-test"] } }
- os: osx
env:
- SET=test01
- EXTRA=CMD_CFLAGS="-mmacosx-version-min=10.7"
- EXTRA1=CMD_CXXFLAGS="-mmacosx-version-min=10.7 -std=c++11 -stdlib=libc++"
- EXTRA2=CMD_LDXFLAGS="-mmacosx-version-min=10.7 -std=c++11 -stdlib=libc++"
compiler: clang
# MacOS build
- env: SET=test01 BCFG=debug
# SNCSEQ 2.2.7 fails to build on MacOS; currently needs master
- env: SET=test01 SNCSEQ=master
os: osx
compiler: clang
- env: SET=test01 WINE=32 TEST=NO
- env: SET=test01 WINE=32 TEST=NO BCFG=static
- env: SET=test01 WINE=32 TEST=NO BCFG=debug
- env: SET=test01 WINE=32 TEST=NO BCFG=static-debug
# Base 3.15 builds
# ================
- env: SET=test01 WINE=64 TEST=NO
- env: SET=test01 WINE=64 TEST=NO BCFG=static
- env: SET=test01 WINE=64 TEST=NO BCFG=debug
- env: SET=test01 WINE=64 TEST=NO BCFG=static-debug
- env: BASE=R3.15.7 SET=test01
- env: SET=test01 RTEMS=4.9 BASE=3.15 TEST=NO
- env: SET=test01 RTEMS=4.9 BASE=3.15 TEST=NO BCFG=static
- env: SET=test01 RTEMS=4.9 BASE=3.15 TEST=NO BCFG=debug
- env: SET=test01 RTEMS=4.9 BASE=3.15 TEST=NO BCFG=static-debug
- env: BASE=R3.15.7 SET=test01 WINE=64 TEST=NO STATIC=YES
dist: bionic
compiler: mingw
- env: SET=test01 RTEMS=4.10
- env: SET=test01 RTEMS=4.10 BCFG=static
- env: SET=test01 RTEMS=4.10 BCFG=debug
- env: SET=test01 RTEMS=4.10 BCFG=static-debug
# The DLL build for this Base version is known to fail
# - env: BASE=R3.15.7 SET=test01 WINE=64 TEST=NO STATIC=NO
# dist: bionic
# compiler: mingw
- env: SET=test01
os: windows
- env: SET=test01 BCFG=static
os: windows
- env: SET=test01 BCFG=debug
os: windows
- env: SET=test01 BCFG=static-debug
os: windows
# Cross-compilation to RTEMS
# (needs EPICS Base >= 3.16.2)
- env: SET=test01
compiler: vs2017
os: windows
- env: SET=test01 BCFG=static
compiler: vs2017
os: windows
- env: SET=test01 BCFG=debug
compiler: vs2017
os: windows
- env: SET=test01 BCFG=static-debug
compiler: vs2017
os: windows
- env: BASE=R3.16.2 SET=test01 RTEMS=4.10
dist: trusty
- env: BASE=R3.16.2 SET=test01 RTEMS=4.9
dist: trusty
# SNCSEQ 2.2.7 fails to build on MacOS; currently needs master
- env: BASE=R3.15.7 SET=test01 SNCSEQ=master
os: osx
compiler: clang
# Base 3.14 builds
# ================
- env: BASE=R3.14.12.8 SET=test01
- env: BASE=R3.14.12.8 SET=test01 WINE=64 TEST=NO STATIC=YES
dist: bionic
compiler: mingw
# The DLL build for this Base version is known to fail
# - env: BASE=R3.14.12.8 SET=test01 WINE=64 TEST=NO STATIC=NO
# dist: bionic
# compiler: mingw
# SNCSEQ 2.2.7 fails to build on MacOS; currently needs master
- env: BASE=R3.14.12.8 SET=test01 SNCSEQ=master
os: osx
compiler: clang

281
README.md
View File

@@ -1,9 +1,8 @@
<a target="_blank" href="http://semver.org">![Version][badge.version]</a>
<a target="_blank" href="https://travis-ci.org/epics-base/ci-scripts">![Travis status][badge.travis]</a>
<a target="_blank" href="https://ci.appveyor.com/project/epics-base/ci-scripts">![AppVeyor status][badge.appveyor]</a>
<a target="_blank" href="https://github.com/epics-base/ci-scripts/actions">![GitHub Actions status][badge.gh-actions]</a>
# Continuous Integration for EPICS Modules
# Continuous Integration Scripts for EPICS Modules
The scripts inside this repository are intended to provide a common,
easy-to-use and flexible way to add Continuous Integration to EPICS
@@ -17,96 +16,62 @@ including a way to specify sets of dependent modules
By using the submodule mechanism, your module will always use an
explicit commit, i.e. a fixed version of the scripts.
This ensures that any further development of the ci-scripts will
never break your existing use.
never break existing use.
## This Repository
In addition to the script that runs the builds and tests, this repository
contains service specific documentation and example configuration files
(in the subdirectories), and a small test suite that is used to verify
functionality and features of the ci-scripts module itself
In addition to the scripts themselves (in the subdirectories),
this repository contains the test suite that is used to verify
functionality and features of the ci-scripts.
The example files are your best reference. They are kept up-to-date and
show a fully-featured and a minimal setup.
You are welcome to use the test suite as a secondary reference, but keep in
mind that in your main module the path to the scripts has one level more
(e.g., `./abc` here would be `./.ci/abc` in your
You are welcome to use the test suite as a reference, but keep in
mind that in your module the path to the scripts has one level more
(e.g., `./travis/abc` here would be `./.ci/travis/abc` in your
module).
Also, the test suite does not show the same quality and documentation
levels as the example files.
Also, a test suite might not show the same level of quality as an
example.
## Features
- Compile against different branches or releases of EPICS Base and
additional dependencies (modules like asyn, std, sequencer, etc.).
additional dependencies (modules like asyn, std, etc.).
- Define setup files that declare sets of dependencies with their
versions and locations.
- Define settings files that declare sets of dependencies
with their versions and locations.
- Define hooks for any dependency.
- Define hook scripts for any dependency.
Hooks are run on the dependency module before it is compiled, so
the module can be patched or further configured.
- Define shared (default) or static builds (for executables and libraries).
- Define optimized (default) or debug builds.
- Define static or shared builds (executables, libraries).
- Run tests (using the EPICS build system, i.e., `make runtests`
and friends).
- Run tests (using the EPICS unit test suite).
## Supported CI Services
### [Travis-CI](https://travis-ci.org/)
- Five parallel runners on Linux/Windows (one runner on MacOS)
- Ubuntu 12/14/16/18, MacOS 10.13, Windows Server v1809
- Compile natively on Linux (different versions of gcc, clang)
- Compile natively on MacOS (clang)
- Compile natively on Windows (gcc/MinGW, Visual Studio 2017)
### Travis-CI
- Use different compilers (gcc, clang)
- Use different gcc versions
- Cross-compile for Windows 32bit and 64bit using MinGW and WINE
- Cross-compile for RTEMS 4.9 and 4.10 (Base >= 3.15)
- Built dependencies are cached (for faster builds).
See specific
**[ci-scripts on Travis-CI README](travis/README.md)**
for more details.
- Cross-compile for RTEMS 4.9 and 4.10 (Base >= 3.16.2)
- Compile on MacOS
- Built dependencies are cached (for faster builds)
### [AppVeyor](https://www.appveyor.com/)
- One parallel runner (all builds are sequential)
- Windows Server 2012/2016/2019
- Compile using gcc/MinGW or different Visual Studio versions: \
2008, 2010, 2012, 2013, 2015, 2017, 2019
### AppVeyor
- Use different compilers (Visual Studio, MinGW)
- Use different Visual Studio versions: \
2008, 2010, 2012, 2013, 2015, 2017, 2019
- Compile for Windows 32bit and 64bit
- No useful caching available.
See specific
**[ci-scripts on AppVeyor README](appveyor/README.md)**
for more details.
### [GitHub Actions](https://github.com/)
- 20 parallel runners on Linux/Windows (5 runners on MacOS)
- Ubuntu 16/18/20, MacOS 10.15, Windows Server 2016/2019
- Compile natively on Linux (gcc, clang)
- Compile natively on MacOS (clang)
- Compile natively on Windows (gcc/MinGW, Visual Studio 2017 & 2019)
- Cross-compile for Windows 32bit and 64bit using MinGW and WINE
- Cross-compile for RTEMS 4.9 and 4.10 (Base >= 3.15)
- Caching not supported by ci-scripts yet.
See specific
**[ci-scripts on GitHub Actions README](github-actions/README.md)**
for more details.
## How to Use the CI-Scripts
1. Get an account on a supported CI service provider platform
1. Get an account on a supported CI service provider platform.
(e.g. [Travis-CI](https://travis-ci.org/),
[AppVeyor](https://www.appveyor.com/), ...).
GitHub Actions does not require a separate account.
[AppVeyor](https://www.appveyor.com/), Azure Pipelines...)
(More details in the specific README of the subdirectory.)
2. In your module, add this ci-scripts repository
2. In your Support Module, add this ci-scripts repository
as a Git Submodule (name suggestion: `.ci`).
```bash
git submodule add https://github.com/epics-base/ci-scripts .ci
@@ -125,8 +90,8 @@ for more details.
```
will compile against the EPICS Base 3.15 branch, the Sequencer
release 2.2.8 and release 4.34 of asyn.
(Any settings can be overridden from the specific job line
in the service configuration, e.g., `.travis.yml`.)
(Any settings can be overridden from the specific job configuration
in e.g. `.travis.yml`.)
4. Create a configuration for the CI service by copying one of
the examples provided in the service specific subdirectory
@@ -136,41 +101,14 @@ for more details.
5. Push your changes and check the CI service for your build results.
## Calling the cue.py Script
Independent from CI service and platform, the runner script is called
from your main configuration as:
`python .ci/cue.py <action>`
where `<action>` is one of:
`prepare`\
Prepare the build by cloning Base and the configured dependency modules,
set up the EPICS build system, then
compile Base and these modules in the order they appear in the `MODULES`
setting.
`build`\
Build your main module.
`test`\
Run the tests of your main module.
`test-results`\
Collect the results of your tests and print a summary.
`exec`\
Execute the remainder of the line using the default command shell.
## Setup Files
Your module might depend on EPICS Base and a few other support modules.
(E.g., a specific driver might need StreamDevice, ASYN and the Sequencer.)
In that case, building against every possible combination of released
versions of those dependencies is not possible:
Base (39) x StreamDevice (50) x ASYN (40) x Sequencer (52) would produce
more than 4 million different combinations, i.e. build jobs.
Base (37) x StreamDevice (50) x ASYN (40) x Sequencer (51) would produce
more than 3.7 million different combinations, i.e. build jobs.
A more reasonable approach is to create a few setups, each being a
combination of dependency releases, that do a few scans of the available
@@ -178,23 +116,19 @@ combination of dependency releases, that do a few scans of the available
for stable versions that many of your users have in production, one for the
latest released versions and one for the development branches.
A job uses a setup file if `SET=<setup>` (without the `.set` extension
of the setup file) is set for the job in the main configuration file.
## Setup File Syntax
Setup files are loaded by the build script. They are found by searching
Setup files are loaded by the build scripts. They are found by searching
the locations in `SETUP_PATH` (space or colon separated list of directories,
relative to your module's root directory).
Setup files can include other setup files by calling `include <setup>`
(again omitting the `.set` extension of the setup file). The configured
(omitting the `.set` extension of the setup file). The configured
`SETUP_PATH` is searched for the include.
Any `VAR=value` setting of a variable in a setup file is only executed if
`VAR` is unset or empty.
That way any settings can be overridden by setting them in the job
description inside the main configuration file (e.g., `.travis.yml`).
Any `VAR=value` setting of a variable is only executed if `VAR` is unset or
empty. That way any settings can be overridden by settings in the main
configuration (e.g., `.travis.yml`).
Empty lines or lines starting with `#` are ignored.
@@ -203,9 +137,8 @@ by using their well-known slugs, separated by spaces.
EPICS Base (slug: `base`) will always be a dependency and will be added and
compiled first. The other dependencies are added and compiled in the order
they are defined in `MODULES`.
Modules needed only for specific jobs (e.g., on specific architectures)
can be added from the main configuration file by setting `ADD_MODULES`
can be added in the main configuration file by setting `ADD_MODULES`
for the specific job(s).
`REPOOWNER=<name>` sets the default GitHub owner (or organization) for all
@@ -241,11 +174,10 @@ recursing into submodules. [default is including submodules: `YES`]
be always be extended by the release or branch name as `<name>-<version>`.
[default is the slug in lower case: `foo`]
`FOO_HOOK=<hook>` Set the name of a `.patch` file, a `.zip` or `.7z` archive
or a script that will be applied (using `-p1`), extracted or run after cloning
the module, before compiling it.
Working directory is the root of the targeted module,
e.g., `.../.cache/foo-1.2`). [default: no hook]
`FOO_HOOK=<script>` Set the name of a script that will be run after cloning
the module, before compiling it. Working directory when running the script
is the root of the targeted module (e.g. `.../.cache/foo-1.2`).
[default: no hooks are run]
`FOO_VARNAME=<name>` Set the name that is used for the module when creating
the `RELEASE.local` files. [default is the slug in upper case: `FOO`]
@@ -262,140 +194,55 @@ specific job will run the job with high verbosity,
printing every command as it is being executed and switching the dependency
builds to higher verbosity.
For debugging on your local machine, you may set `CACHEDIR` to change the
location for the dependency builds. [default is `$HOME/.cache`]
Set `PARALLEL_MAKE` to the number of parallel make jobs that you want your
build to use. [default is the number of CPUs on the runner]
Set `CLEAN_DEPS` to `NO` if you want to leave the object file directories
(`**/O.*`) in the cached dependencies. [default is to run `make clean`
after building a dependency]
Service specific options are described in the README files
in the service specific subdirectories:
- [Travis-CI README](travis/README.md)
- [AppVeyor README](appveyor/README.md)
For local debugging, you may set `CACHEDIR` to change the location for the
dependency builds. [default is `$HOME/.cache`]
## References: EPICS Modules Using ci-scripts
[EPICS Base](https://github.com/epics-base/epics-base) and its submodules
[pvData](https://github.com/epics-base/pvDataCPP),
EPICS Base: [pvData](https://github.com/epics-base/pvDataCPP),
[pvAccess](https://github.com/epics-base/pvAccessCPP),
[pva2pva](https://github.com/epics-base/pva2pva),
[PVXS](https://github.com/mdavidsaver/pvxs)
[pva2pva](https://github.com/epics-base/pva2pva)
EPICS Modules:
[ASYN](https://github.com/epics-modules/asyn),
[autosave](https://github.com/epics-modules/autosave),
[busy](https://github.com/epics-modules/busy),
[devlib2](https://github.com/epics-modules/devlib2),
[ecmc](https://github.com/epics-modules/ecmc),
[gtest](https://github.com/epics-modules/gtest),
[ip](https://github.com/epics-modules/ip),
[lua](https://github.com/epics-modules/lua),
[MCoreUtils](https://github.com/epics-modules/MCoreUtils),
[modbus](https://github.com/epics-modules/modbus),
[motor](https://github.com/epics-modules/motor),
[mrfioc2](https://github.com/epics-modules/mrfioc2),
[OPCUA](https://github.com/ralphlange/opcua),
[PCAS](https://github.com/epics-modules/pcas),
[softGlueZync](https://github.com/epics-modules/softGlueZynq),
[sscan](https://github.com/epics-modules/sscan),
[std](https://github.com/epics-modules/std),
[vac](https://github.com/epics-modules/vac),
[xxx](https://github.com/epics-modules/xxx)
EPICS Modules: [PCAS](https://github.com/epics-modules/pcas),
[ASYN](https://github.com/epics-modules/asyn)
ESS: [EtherCAT MC Motor Driver][ref.ethercatmc]
ESS: [Motor driver (model 3) for EtherCAT Motion Controller][ref.ethercatmc]
## Migration Hints
Look for changes in the example configuration files, and check how they
apply to your module.
If comments in the example have changed, copy them to your configuration
to always have up-to-date documentation in your file.
### 2.x to 3.x Migration
Update the script and test settings in your configuration to call the
new script, following the example file.
`python .ci/cue.py <action>`
#### AppVeyor
The `configuration:` setting options have changed; they are now
`default`, `static`, `debug` and `static-debug`.
MinGW builds are now using the `CMP: gcc` compiler setting.
Adding arguments to make is supported through the `EXTRA` .. `EXTRA5`
variables. Each variable value will be passed as one argument.
#### Travis
The new `BCFG` (build configuration) variable accepts the same options as
the AppVeyor `configuration:` setting. Replace any`STATIC=YES` settings with
`BCFG=static`.
Remove `bash` in the `homebrew:` section of `addons:`. There are no more
bash scripts.
MinGW builds (cross-builds using WINE as well as native builds on Windows)
are now using the `gcc` compiler setting.
Since `gcc` is the default, you can simply remove `compiler: mingw` lines.
For Windows, Travis offers native MinGW and Visual Studio 2017 compilers.
Use `os: windows` and set `compiler:` to `gcc` or `vs2017`
for those builds.
Chocolatey packages to be installed for the Windows jobs are set by adding
them to the environment variable `CHOCO`.
ITER: [OPC UA Device Support](https://github.com/ralphlange/opcua)
## Frequently Asked Questions
##### How can I see what the dependency building jobs are actually doing?
**How can I see what the dependency building jobs are actually doing?**
Set `VV=1` in the configuration line of the job you are interested in.
This will make all builds (not just for your module) verbose.
##### How do I update my module to use a newer minor release of ci-scripts?
**How do I update my module to use a newer release of ci-scripts?**
Update the submodule in `.ci` first, then change your CI configuration
(if needed) and commit both to your module. E.g., to update your Travis
setup to release 3.1.1 of ci-scripts:
setup to release 2.2.1 of ci-scripts:
```bash
cd .ci
git pull origin v3.1.1
git pull origin v2.2.1
cd -
git add .ci
# if needed:
edit .travis.yml # and/or AppVeyor/GitHub Actions configuration
edit .travis.yml
git add .travis.yml
git commit -m "Update ci-scripts submodule to v3.1.1"
git commit -m "Update ci-scripts submodule to v2.2.1"
```
Check the example configuration files inside ci-scripts (and their
changes) to see what might be needed and/or interesting to change
in your configuration.
Depending on the changes contained in the ci-scripts update, it might
be advisable to clear the CI caches after updating ci-scripts. E.g.,
a change in setting up EPICS Base will not be applied if Base is found
in the cache.
**Why does running the scripts locally on my MacOS machine fail?**
##### How do I add a dependency module only for a specific job?
Add the additional dependency in the main configuration file by setting
`ADD_MODULES` for the specific job(s).
##### Why the name _cue_?
The noun _cue_ is defined as "_a signal (such as a word, phrase, or bit of
stage business) to a performer to begin a specific speech or action_".
(Merriam-Webster)
The ci-scripts for Travis-CI require Bash version 4.
As Apple ships an older Bash for [political reasons][reddit.bash],
you need to install a more recent Bash, e.g. using MacPorts
or Homebrew.
## Release Numbering of this Module
@@ -410,7 +257,6 @@ is created.
Minor release numbers refer to additions and enhancements that do not
require the configuration inside an existing user module to be changed.
(Unless for using a new feature.)
Again: using the git submodule mechanism to include these scripts means
that user modules always work with a fixed, frozen version.
@@ -427,8 +273,7 @@ in file LICENSE that is included with this distribution.
<!-- Links -->
[badge.version]: https://badge.fury.io/gh/epics-base%2Fci-scripts.svg
[badge.travis]: https://travis-ci.org/epics-base/ci-scripts.svg?branch=master
[badge.appveyor]: https://ci.appveyor.com/api/projects/status/8b578alg974axvux?svg=true
[badge.gh-actions]: https://github.com/epics-base/ci-scripts/workflows/ci-scripts%20build/test/badge.svg
[badge.appveyor]: https://ci.appveyor.com/api/projects/status/xwdv8fpxu0byp3hn?svg=true
[reddit.bash]: https://www.reddit.com/r/bash/comments/393oqv/why_is_the_version_of_bash_included_in_os_x_so_old/

View File

@@ -1,39 +1,7 @@
diff --git a/config/RULES.Db b/config/RULES.Db
index b4946c7aa..90b76ed08 100644
--- a/config/RULES.Db
+++ b/config/RULES.Db
@@ -12,11 +12,7 @@
#
MAKEBPT = $(EPICS_BASE_HOST_BIN)/makeBpt$(EXE)
-ifndef MSI
-# Tool from R3.14 extensions bin, R3.13 extensions bin, or user path
-MSI = $(firstword $(wildcard $(EPICS_EXTENSIONS_HOST_BIN)/msi$(HOSTEXE) \
- $(EPICS_EXTENSIONS)/bin/$(HOST_ARCH)/msi$(HOSTEXE)) msi$(HOSTEXE))
-endif
+MSI = $(EPICS_BASE_HOST_BIN)/msi$(HOSTEXE)
DBEXPAND = $(EPICS_BASE_HOST_BIN)/dbExpand$(EXE)
DBST = dbst
diff --git a/configure/CONFIG_BASE b/configure/CONFIG_BASE
index 7ee5a5b89..9a9793093 100644
--- a/configure/CONFIG_BASE
+++ b/configure/CONFIG_BASE
@@ -112,8 +112,5 @@ ifndef DBST
DBST = dbst
endif
-ifndef MSI
-MSI = msi
-endif
-
+MSI = $(EPICS_BASE_HOST_BIN)/msi$(HOSTEXE)
diff --git a/src/dbtools/Makefile b/src/dbtools/Makefile
index 38ed52c9e..8655a5337 100644
--- a/src/dbtools/Makefile
+++ b/src/dbtools/Makefile
@@ -11,6 +11,11 @@ TOP=../..
diff -ruN ../3.14/src/dbtools/Makefile ./src/dbtools/Makefile
--- ../3.14/src/dbtools/Makefile 2017-03-16 21:37:51.278140900 +0100
+++ ./src/dbtools/Makefile 2020-04-06 12:40:51.723550846 +0200
@@ -11,6 +11,11 @@
include $(TOP)/configure/CONFIG
@@ -45,11 +13,9 @@ index 38ed52c9e..8655a5337 100644
INC += dbLoadTemplate.h
INC += dbtoolsIocRegister.h
diff --git a/src/dbtools/msi.c b/src/dbtools/msi.c
new file mode 100644
index 000000000..525d4f25b
--- /dev/null
+++ b/src/dbtools/msi.c
diff -ruN ../3.14/src/dbtools/msi.c ./src/dbtools/msi.c
--- ../3.14/src/dbtools/msi.c 1970-01-01 01:00:00.000000000 +0100
+++ ./src/dbtools/msi.c 2013-05-13 19:00:43.000000000 +0200
@@ -0,0 +1,798 @@
+/*************************************************************************\
+* Copyright (c) 2002 The University of Chicago, as Operator of Argonne

394
appveyor-test.py Normal file
View File

@@ -0,0 +1,394 @@
#!/usr/bin/env python
"""Module ci-scripts AppVeyor unit tests
"""
# SET=test00 in the environment (.appveyor.yml) runs the tests in this script
# all other jobs are started as compile jobs
from __future__ import print_function
import sys, os, shutil, fileinput
import distutils.util
import re
import subprocess as sp
import unittest
import logging
from argparse import Namespace
builddir = os.getcwd()
def find_in_file(regex, filename):
file = open (filename, "r")
for line in file:
if re.search(regex, line):
return True
return False
def getStringIO():
if (sys.version_info > (3, 0)):
import io
return io.StringIO()
else:
import StringIO
return StringIO.StringIO()
sys.path.append('appveyor')
import do
# we're working with tags (detached heads) a lot: suppress advice
do.call_git(['config', '--global', 'advice.detachedHead', 'false'])
class TestSourceSet(unittest.TestCase):
def setUp(self):
os.environ['SETUP_PATH'] = '.:appveyor'
if 'BASE' in os.environ:
del os.environ['BASE']
do.clear_lists()
os.chdir(builddir)
def test_EmptySetupDirsPath(self):
del os.environ['SETUP_PATH']
self.assertRaisesRegexp(NameError, '\(SETUP_PATH\) is empty', do.source_set, 'test01')
def test_InvalidSetupName(self):
self.assertRaisesRegexp(NameError, 'does not exist in SETUP_PATH', do.source_set, 'xxdoesnotexistxx')
def test_ValidSetupName(self):
capturedOutput = getStringIO()
sys.stdout = capturedOutput
do.source_set('test01')
sys.stdout = sys.__stdout__
self.assertEqual(do.setup['BASE'], '7.0', 'BASE was not set to \'7.0\'')
def test_SetupDoesNotOverridePreset(self):
os.environ['BASE'] = 'foo'
capturedOutput = getStringIO()
sys.stdout = capturedOutput
do.source_set('test01')
sys.stdout = sys.__stdout__
self.assertEqual(do.setup['BASE'], 'foo',
'Preset BASE was overridden by test01 setup (expected \'foo\' got {0})'
.format(do.setup['BASE']))
def test_IncludeSetupFirstSetWins(self):
capturedOutput = getStringIO()
sys.stdout = capturedOutput
do.source_set('test02')
sys.stdout = sys.__stdout__
self.assertEqual(do.setup['BASE'], 'foo',
'BASE set in test02 was overridden by test01 setup (expected \'foo\' got {0})'
.format(do.setup['BASE']))
self.assertEqual(do.setup['FOO'], 'bar', 'Setting of single word does not work')
self.assertEqual(do.setup['FOO2'], 'bar bar2', 'Setting of multiple words does not work')
self.assertEqual(do.setup['FOO3'], 'bar bar2', 'Indented setting of multiple words does not work')
self.assertEqual(do.setup['SNCSEQ'], 'R2-2-7', 'Setup test01 was not included')
def test_DoubleIncludeGetsIgnored(self):
capturedOutput = getStringIO()
sys.stdout = capturedOutput
do.source_set('test03')
sys.stdout = sys.__stdout__
self.assertRegexpMatches(capturedOutput.getvalue(), 'Ignoring already included setup file')
class TestUpdateReleaseLocal(unittest.TestCase):
release_local = os.path.join(do.cachedir, 'RELEASE.local')
def setUp(self):
if os.path.exists(self.release_local):
os.remove(self.release_local)
os.chdir(builddir)
def test_SetModule(self):
do.update_release_local('MOD1', '/foo/bar')
found = 0
for line in fileinput.input(self.release_local, inplace=1):
if 'MOD1=' in line:
self.assertEqual(line.strip(), 'MOD1=/foo/bar', 'MOD1 not set correctly')
found += 1
fileinput.close()
self.assertEqual(found, 1, 'MOD1 not written once to RELEASE.local (found {0})'.format(found))
def test_SetBaseAndMultipleModules(self):
do.update_release_local('EPICS_BASE', '/bar/foo')
do.update_release_local('MOD1', '/foo/bar')
do.update_release_local('MOD2', '/foo/bar2')
do.update_release_local('MOD1', '/foo/bar1')
found = {}
foundat = {}
for line in fileinput.input(self.release_local, inplace=1):
if 'MOD1=' in line:
self.assertEqual(line.strip(), 'MOD1=/foo/bar1',
'MOD1 not set correctly (expected \'MOD1=/foo/bar1\' found \'{0}\')'
.format(line))
if 'mod1' in found:
found['mod1'] += 1
else:
found['mod1'] = 1
foundat['mod1'] = fileinput.filelineno()
if 'MOD2=' in line:
self.assertEqual(line.strip(), 'MOD2=/foo/bar2',
'MOD2 not set correctly (expected \'MOD2=/foo/bar2\' found \'{0}\')'
.format(line))
if 'mod2' in found:
found['mod2'] += 1
else:
found['mod2'] = 1
foundat['mod2'] = fileinput.filelineno()
if 'EPICS_BASE=' in line:
self.assertEqual(line.strip(), 'EPICS_BASE=/bar/foo',
'EPICS_BASE not set correctly (expected \'EPICS_BASE=/bar/foo\' found \'{0}\')'
.format(line))
if 'base' in found:
found['base'] += 1
else:
found['base'] = 1
foundat['base'] = fileinput.filelineno()
fileinput.close()
self.assertEqual(found['mod1'], 1,
'MOD1 does not appear once in RELEASE.local (found {0})'.format(found['mod1']))
self.assertEqual(found['mod2'], 1,
'MOD2 does not appear once in RELEASE.local (found {0})'.format(found['mod2']))
self.assertEqual(found['base'], 1,
'EPICS_BASE does not appear once in RELEASE.local (found {0})'.format(found['base']))
self.assertGreater(foundat['base'], foundat['mod2'],
'EPICS_BASE (line {0}) appears before MOD2 (line {1})'
.format(foundat['base'], foundat['mod2']))
self.assertGreater(foundat['mod2'], foundat['mod1'],
'MOD2 (line {0}) appears before MOD1 (line {1})'.format(foundat['mod2'], foundat['mod1']))
class TestAddDependencyUpToDateCheck(unittest.TestCase):
hash_3_15_6 = "ce7943fb44beb22b453ddcc0bda5398fadf72096"
location = os.path.join(do.cachedir, 'base-R3.15.6')
licensefile = os.path.join(location, 'LICENSE')
checked_file = os.path.join(location, 'checked_out')
release_file = os.path.join(location, 'configure', 'RELEASE')
def setUp(self):
os.environ['SETUP_PATH'] = '.:appveyor'
if os.path.exists(self.location):
shutil.rmtree(self.location, onerror=do.remove_readonly)
do.clear_lists()
os.chdir(builddir)
do.source_set('defaults')
do.complete_setup('BASE')
def test_MissingDependency(self):
do.setup['BASE'] = 'R3.15.6'
do.add_dependency('BASE')
self.assertTrue(os.path.exists(self.licensefile), 'Missing dependency was not checked out')
self.assertTrue(os.path.exists(self.checked_file), 'Checked-out commit marker was not written')
with open(self.checked_file, 'r') as bfile:
checked_out = bfile.read().strip()
bfile.close()
self.assertEqual(checked_out, self.hash_3_15_6,
'Wrong commit of dependency checked out (expected=\"{0}\" found=\"{1}\")'
.format(self.hash_3_15_6, checked_out))
self.assertFalse(find_in_file('include \$\(TOP\)/../RELEASE.local', self.release_file),
'RELEASE in Base includes TOP/../RELEASE.local')
def test_UpToDateDependency(self):
do.setup['BASE'] = 'R3.15.6'
do.add_dependency('BASE')
os.remove(self.licensefile)
do.add_dependency('BASE')
self.assertFalse(os.path.exists(self.licensefile), 'Check out on top of existing up-to-date dependency')
def test_OutdatedDependency(self):
do.setup['BASE'] = 'R3.15.6'
do.add_dependency('BASE')
os.remove(self.licensefile)
with open(self.checked_file, "w") as fout:
print('XXX not the right hash XXX', file=fout)
fout.close()
do.add_dependency('BASE')
self.assertTrue(os.path.exists(self.licensefile), 'No check-out on top of out-of-date dependency')
with open(self.checked_file, 'r') as bfile:
checked_out = bfile.read().strip()
bfile.close()
self.assertEqual(checked_out, self.hash_3_15_6,
"Wrong commit of dependency checked out (expected='{0}' found='{1}')"
.format(self.hash_3_15_6, checked_out))
def is_shallow_repo(place):
check = sp.check_output(['git', 'rev-parse', '--is-shallow-repository'], cwd=place).strip()
if check == '--is-shallow-repository':
if os.path.exists(os.path.join(place, '.git', 'shallow')):
check = 'true'
else:
check = 'false'
return check == 'true'
class TestAddDependencyOptions(unittest.TestCase):
location = os.path.join(do.cachedir, 'mcoreutils-master')
testfile = os.path.join(location, '.ci', 'LICENSE')
def setUp(self):
os.environ['SETUP_PATH'] = '.:appveyor'
if os.path.exists(do.cachedir):
shutil.rmtree(do.cachedir, onerror=do.remove_readonly)
do.clear_lists()
do.source_set('defaults')
do.complete_setup('MCoreUtils')
do.setup['MCoreUtils'] = 'master'
def test_Default(self):
do.add_dependency('MCoreUtils')
self.assertTrue(os.path.exists(self.testfile),
'Submodule (.ci) not checked out recursively (requested: default=YES')
self.assertTrue(is_shallow_repo(self.location),
'Module not checked out shallow (requested: default=5)')
def test_SetRecursiveNo(self):
do.setup['MCoreUtils_RECURSIVE'] = 'NO'
do.add_dependency('MCoreUtils')
self.assertFalse(os.path.exists(self.testfile), 'Submodule (.ci) checked out recursively')
def test_SetDepthZero(self):
do.setup['MCoreUtils_DEPTH'] = '0'
do.add_dependency('MCoreUtils')
self.assertFalse(is_shallow_repo(self.location), 'Module checked out shallow (requested full)')
def test_SetDepthThree(self):
do.setup['MCoreUtils_DEPTH'] = '3'
do.add_dependency('MCoreUtils')
self.assertTrue(is_shallow_repo(self.location),
'Module not checked out shallow (requested: default=5)')
def test_AddMsiTo314(self):
do.complete_setup('BASE')
do.setup['BASE'] = 'R3.14.12.1'
msifile = os.path.join(do.cachedir, 'base-R3.14.12.1', 'src', 'dbtools', 'msi.c')
do.add_dependency('BASE')
self.assertTrue(os.path.exists(msifile), 'MSI was not added to Base 3.14')
def repo_access(dep):
do.set_setup_from_env(dep)
do.setup.setdefault(dep + "_DIRNAME", dep.lower())
do.setup.setdefault(dep + "_REPONAME", dep.lower())
do.setup.setdefault('REPOOWNER', 'epics-modules')
do.setup.setdefault(dep + "_REPOOWNER", do.setup['REPOOWNER'])
do.setup.setdefault(dep + "_REPOURL", 'https://github.com/{0}/{1}.git'
.format(do.setup[dep + '_REPOOWNER'], do.setup[dep + '_REPONAME']))
with open(os.devnull, 'w') as devnull:
return do.call_git(['ls-remote', '--quiet', '--heads', do.setup[dep + '_REPOURL']],
stdout=devnull, stderr=devnull)
class TestDefaultModuleURLs(unittest.TestCase):
modules = ['BASE', 'PVDATA', 'PVACCESS', 'NTYPES',
'SNCSEQ', 'STREAM', 'ASYN', 'STD',
'CALC', 'AUTOSAVE', 'BUSY', 'SSCAN',
'IOCSTATS', 'MOTOR', 'IPAC', ]
def setUp(self):
os.environ['SETUP_PATH'] = '.:appveyor'
do.clear_lists()
os.chdir(builddir)
do.source_set('defaults')
def test_Repos(self):
for mod in self.modules:
self.assertEqual(repo_access(mod), 0, 'Defaults for {0} do not point to a valid git repository at {1}'
.format(mod, do.setup[mod + '_REPOURL']))
class TestVCVars(unittest.TestCase):
def test_vcvars(self):
if ('CMP' in os.environ and os.environ['CMP'] in ('mingw',)) \
or distutils.util.get_platform() != "win32":
raise unittest.SkipTest()
do.with_vcvars('env')
class TestSetupForBuild(unittest.TestCase):
configuration = os.environ['CONFIGURATION']
platform = os.environ['PLATFORM']
cc = os.environ['CMP']
args = Namespace(paths=[])
def setUp(self):
os.environ.pop('EPICS_HOST_ARCH', None)
def tearDown(self):
os.environ['CONFIGURATION'] = self.configuration
os.environ['PLATFORM'] = self.platform
os.environ['CMP'] = self.cc
def test_AddPathsOption(self):
os.environ['FOOBAR'] = 'BAR'
args = Namespace(paths=['/my/{FOOBAR}/dir', '/my/foobar'])
do.setup_for_build(args)
self.assertTrue(re.search('/my/BAR/dir', os.environ['PATH']), 'Expanded path not in PATH')
self.assertTrue(re.search('/foobar', os.environ['PATH']), 'Plain path not in PATH')
os.environ.pop('FOOBAR', None)
def test_HostArchConfiguration(self):
for config in ['dynamic', 'dynamic-debug', 'static', 'static-debug']:
os.environ['CONFIGURATION'] = config
do.setup_for_build(self.args)
self.assertTrue('EPICS_HOST_ARCH' in os.environ,
'EPICS_HOST_ARCH is not set for Configuration={0}'.format(config))
if re.search('static', config):
self.assertTrue(re.search('-static$', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is not -static for Configuration={0}'.format(config))
self.assertFalse(re.search('debug', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is -debug for Configuration={0}'.format(config))
elif re.search('debug', config):
self.assertFalse(re.search('static', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is -static for Configuration={0}'.format(config))
self.assertTrue(re.search('-debug$', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is not -debug for Configuration={0}'.format(config))
else:
self.assertFalse(re.search('static', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is -static for Configuration={0}'.format(config))
self.assertFalse(re.search('debug', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is -debug for Configuration={0}'.format(config))
def test_HostArchPlatform(self):
for platform in ['x86', 'x64', 'X64']:
for cc in ['vs2019', 'mingw']:
os.environ['PLATFORM'] = platform
os.environ['CMP'] = cc
os.environ['CONFIGURATION'] = 'dynamic'
do.setup_for_build(self.args)
self.assertTrue('EPICS_HOST_ARCH' in os.environ,
'EPICS_HOST_ARCH is not set for {0} / {1}'.format(cc, platform))
if platform == 'x86':
self.assertTrue(re.search('^win32-x86', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is not win32-x86 for {0} / {1}'.format(cc, platform))
else:
self.assertTrue(re.search('^windows-x64', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is not windows-x64 for {0} / {1}'.format(cc, platform))
if cc == 'mingw':
self.assertTrue(re.search('-mingw$', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is not -mingw for {0} / {1}'.format(cc, platform))
if platform == 'x86':
pattern = 'mingw32'
else:
pattern = 'mingw64'
self.assertTrue(re.search(pattern, os.environ['PATH']),
'Binary location for {0} not in PATH'.format(pattern))
self.assertTrue(re.search(pattern, os.environ['INCLUDE']),
'Include location for {0} not in INCLUDE'.format(pattern))
def test_StrawberryInPath(self):
os.environ['CMP'] = 'vs2019'
do.setup_for_build(self.args)
self.assertTrue(re.search('strawberry', os.environ['PATH'], flags=re.IGNORECASE),
'Strawberry Perl location not in PATH for vs2019')
if __name__ == "__main__":
if 'VV' in os.environ and os.environ['VV'] == '1':
logging.basicConfig(level=logging.DEBUG)
do.silent_dep_builds = False
do.host_info()
if sys.argv[1:]==['env']:
# testing with_vcvars
[print(K,'=',V) for K, V in os.environ.items()]
else:
unittest.main()

View File

@@ -12,6 +12,14 @@
cache:
- C:\Users\appveyor\.tools
#---------------------------------#
# additional packages #
#---------------------------------#
install:
# for the sequencer
- cinst re2c
#---------------------------------#
# repository cloning #
#---------------------------------#
@@ -20,8 +28,6 @@ cache:
init:
# Set autocrlf to make batch files work
- git config --global core.autocrlf true
# print the connection info for RDP connections (see 'debugging' below)
#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
# Set clone depth (do not fetch complete history)
clone_depth: 50
@@ -33,17 +39,6 @@ skip_commits:
- 'templates/*'
- '**/*.html'
- '**/*.md'
- '.travis.yml'
#---------------------------------#
# additional packages #
#---------------------------------#
install:
# fetch submodules (like ci-scripts)
- cmd: git submodule update --init --recursive
# for the sequencer
- cinst re2c
#---------------------------------#
# build matrix configuration #
@@ -53,48 +48,23 @@ install:
# are executed sequentially, each one taking 10-15 minutes.
# Consider this when defining your build matrix. (A full matrix build takes more than 8 hours.)
# Default build worker image
image: Visual Studio 2015
# Build Configurations: shared/static, optimized/debug
# Build Configurations: dll/static, regular/debug
configuration:
- default
- dynamic
- static
- debug
- dynamic-debug
- static-debug
# Environment variables
# Well-known variables to use
# CMP compiler to use ('gcc' for native MinGW, 'vs...' for Visual Studio)
# SET source setup file
# ADD_MODULES extra modules (for a specific job)
# TEST set to NO to skip running the tests (default: YES)
# VV set VV=1 to make build scripts verbose (default: unset)
# EXTRA content will be added to make command line
# EXTRA1..5 more additional arguments for the make command
# (one argument per variable)
# Usually from setup files, but may be specified or overridden
# on a job line
# MODULES list of dependency modules
# BASE branch or release tag name of the EPICS Base to use
# <MODULE> branch or release tag for a specific module
# ... see README for setup file syntax description
# AppVeyor specific
# APPVEYOR_BUILD_WORKER_IMAGE run job using specified VM image
# (not the one from the image: line above)
# Environment variables: compiler toolchain, base version, setup file, ...
environment:
# common / default variables for all jobs
SETUP_PATH: .ci-local:.ci
BASE: 7.0
matrix:
- CMP: vs2019
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
- CMP: gcc
SET: test00
- CMP: mingw
- CMP: vs2019
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
- CMP: vs2019
@@ -137,16 +107,19 @@ matrix:
# building & testing #
#---------------------------------#
install:
- cmd: git submodule update --init --recursive
- cmd: python .ci/appveyor/do.py prepare
build_script:
- cmd: python .ci/cue.py prepare
- cmd: python .ci/cue.py build
- cmd: python .ci/appveyor/do.py build
test_script:
- cmd: python .ci/cue.py test
- cmd: python .ci/appveyor/do.py test
on_finish:
- ps: Get-ChildItem *.tap -Recurse -Force | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
- cmd: python .ci/cue.py test-results
- cmd: python .ci/appveyor/do.py build test-results -s
#---------------------------------#
# debugging #
@@ -156,8 +129,9 @@ on_finish:
## note that you will need to connect within the usual build timeout limit (60 minutes)
## so you may want to adjust the build matrix above to just build the one of interest
# to print the RDP connection info
# uncomment the appropriate line in the init: section above
# print the connection info
#init:
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
# block a failed build (until the watchdog barks)
#on_failure:

View File

@@ -1,6 +1,8 @@
# .appveyor.yml for use with EPICS Base ci-scripts
# (see: https://github.com/epics-base/ci-scripts)
# This is YAML - indentation levels are crucial
cache:
- C:\Users\appveyor\.tools
@@ -15,18 +17,12 @@ skip_commits:
- 'templates/*'
- '**/*.html'
- '**/*.md'
- '.travis.yml'
install:
- cmd: git submodule update --init --recursive
image: Visual Studio 2019
# Build Configurations: shared/static, optimized/debug
# Build Configurations: dll/static, regular/debug
configuration:
- default
- dynamic
# - static
- debug
- dynamic-debug
# - static-debug
environment:
@@ -36,8 +32,10 @@ environment:
matrix:
- CMP: vs2019
BASE: 7.0
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
- CMP: vs2019
BASE: 3.15
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
# Platform: processor architecture
platform:
@@ -55,16 +53,19 @@ matrix:
- platform: x64
CMP: vs2008
install:
- cmd: git submodule update --init --recursive
- cmd: python .ci/appveyor/do.py prepare
build_script:
- cmd: python .ci/cue.py prepare
- cmd: python .ci/cue.py build
- cmd: python .ci/appveyor/do.py build
test_script:
- cmd: python .ci/cue.py test
- cmd: python .ci/appveyor/do.py test
on_finish:
- ps: Get-ChildItem *.tap -Recurse -Force | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
- cmd: python .ci/cue.py test-results
- cmd: python .ci/appveyor/do.py build test-results -s
notifications:
- provider: GitHubPullRequest

View File

@@ -2,18 +2,17 @@
## Features
- One parallel runner (all builds are sequential)
- Windows Server 2012/2016/2019
- Compile using gcc/MinGW or different Visual Studio versions: \
2008, 2010, 2012, 2013, 2015, 2017, 2019
- Use different compilers (Visual Studio, MinGW)
- Use different VS versions (2008, 2010, 2012, 2013, 2015, 2017, 2019)
- Compile for Windows 32bit and 64bit
- No useful caching available.
- Create static libraries or DLLs (plus the matching executables)
- Create optimized or debug builds
## How to Use these Scripts
1. Get an account on [AppVeyor](https://www.appveyor.com/), connect
it to your GitHub account and activate your support module's
repository. For more details, please see below and refer to the
repository. For more details, please refer to the
[AppVeyor documentation](https://www.appveyor.com/docs/).
2. Add the ci-scripts respository as a Git Submodule
@@ -34,82 +33,23 @@
AppVeyor automatically creates a build matrix with the following axes:
1. `configuration:` \
Select shared (DLL) or static as well as optimized or debug builds. \
Default: `shared-optimized`
Select static or dynamic (DLL) as well as regular or debug builds.
2. `platform:` \
Select 32bit or 64bit processor architecture.
3. `environment: / matrix:` \
List of environment variable settings. Each list element (starting with
a dash) is one step on the axis of the build matrix. \
Set `CMP` to select the compiler: `gcc` for the native
Set `CMP` to select the compiler: `mingw` for the native
[MinGW](http://mingw-w64.org/) GNU compiler, `vs2008` ...`vs2019`
(options listed above) for the Microsoft Visual Studio compilers.
Your builds will take long. \
AppVeyor only grants a single parallel runner VM - all jobs of the matrix
are executed sequentially. AppVeyor also does not provide a usable cache
mechanism to retain dependency artifacts across builds.
Each job will take between 6 and 15 minutes, plus testing time, every time.
AppVeyor only grants a single worker VM - all jobs of the matrix are
executed sequentially. Each job will take around 10 minutes.
The `matrix: / exclude:` setting can be used to reduce the number of
jobs. Check the [AppVeyor docs][appveyor.doc.matrix]
for more ways to reduce the build matrix size. \
E.g., you can opt for not creating matrix axes for `configuration:`
and`platform:` by moving these configurations into the job lines
under `environment: / matrix:`.
jobs. Check the [AppVeyor docs](https://www.appveyor.com/docs/build-configuration/#build-matrix)
for more ways to reduce the build matrix size.
6. Push your changes and check
[ci.appveyor.com](https://ci.appveyor.com/) for your build results.
## GitHub / AppVeyor Integration and Authentication
### Security
Enabling Two-Factor-Authentication (2FA) is always a good idea, for all
your web based services, including GitHub and AppVeyor. \
Get an app for your phone (Authy works fine for me, but there are plenty),
and your phone will generate one-time passwords to verify your identity
to the service if required (e.g., when logging in from a new device).
### Authentication
You can use different ways and services to authenticate when you log into
your AppVeyor account. The easiest way - at least when you're using the
service with repositories on GitHub - is to use GitHub authentication.
### GitHub Integration
AppVeyor offers two ways to integrate with GitHub: through a GitHub
application or through an OAuth application. GitHub applications are using
the newer API, allow easier fine-grained access rights tuning and are
preferred.
The differences are mostly visible when you work with repositories under
organizational GitHub accounts: Using OAuth, AppVeyor always has the full
rights of your personal GitHub account.
GitHub applications on the other hand have separate instances and
configuration for every organizational account you are using on GitHub.
### Enabling Builds for your Repository
On the 'Projects' tab of your AppVeyor web interface, create a new project.
If the repository is not listed on the project creation page,
verify the Integration settings. Most of the relevant configuration
is taken from GitHub and has to be set up there.
### AppVeyor Account Sharing
You can always invite other AppVeyor users to have access to an AppVeyor
account, forming a team. Such additional shared accounts are a way to make
the AppVeyor limits (e.g., one parallel builder per account) more manageable.
## Known Issues
#### Build Worker Images
The AppVeyor documentation on build worker images doesn't seem to fully
describe the way things are handled internally.
The tested and suggested reproducible way of defining the build worker image
is shown in the example configuration files:
- Set the default image using the `image:` tag.
- Override the image for specific jobs by setting the
`APPVEYOR_BUILD_WORKER_IMAGE` environment variable.
<!-- Links -->
[appveyor.doc.matrix]: https://www.appveyor.com/docs/build-configuration/#build-matrix

632
appveyor/do.py Normal file
View File

@@ -0,0 +1,632 @@
#!/usr/bin/env python
"""Windows (AppVeyor) ci build script
"""
from __future__ import print_function
import sys, os, stat, shutil
import fileinput
import logging
import re
import subprocess as sp
import distutils.util
logger = logging.getLogger(__name__)
# Setup ANSI Colors
ANSI_RED = "\033[31;1m"
ANSI_GREEN = "\033[32;1m"
ANSI_YELLOW = "\033[33;1m"
ANSI_BLUE = "\033[34;1m"
ANSI_MAGENTA = "\033[35;1m"
ANSI_CYAN = "\033[36;1m"
ANSI_RESET = "\033[0m"
ANSI_CLEAR = "\033[0K"
seen_setups = []
modules_to_compile = []
setup = {}
places = {}
if 'HomeDrive' in os.environ:
cachedir = os.path.join(os.getenv('HomeDrive'), os.getenv('HomePath'), '.cache')
toolsdir = os.path.join(os.getenv('HomeDrive'), os.getenv('HomePath'), '.tools')
elif 'HOME' in os.environ:
cachedir = os.path.join(os.getenv('HOME'), '.cache')
toolsdir = os.path.join(os.getenv('HOME'), '.tools')
else:
cachedir = os.path.join('.', '.cache')
toolsdir = os.path.join('.', '.tools')
if 'CACHEDIR' in os.environ:
cachedir = os.environ['CACHEDIR']
vcvars_table = {
# https://en.wikipedia.org/wiki/Microsoft_Visual_Studio#History
'vs2019':r'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat',
'vs2017':r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat',
'vs2015':r'C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat',
'vs2013':r'C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat',
'vs2012':r'C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat',
'vs2010':r'C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat',
'vs2008':r'C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat',
}
ciscriptsdir = os.path.abspath(os.path.dirname(sys.argv[0]))
if os.path.basename(ciscriptsdir) == 'appveyor':
ciscriptsdir = ciscriptsdir.rstrip(os.pathsep+'appveyor')
if 'BASE' not in os.environ or os.environ['BASE'] == 'SELF':
building_base = True
places['EPICS_BASE'] = '.'
else:
building_base = False
def modlist():
if building_base:
ret = []
else:
for var in ['ADD_MODULES', 'MODULES']:
setup.setdefault(var, '')
if var in os.environ:
setup[var] = os.environ[var]
logger.debug('ENV assignment: %s = %s', var, setup[var])
ret = ['BASE'] + setup['ADD_MODULES'].upper().split() + setup['MODULES'].upper().split()
logger.debug('Effective module list: %s', ret)
return ret
zip7 = r'C:\Program Files\7-Zip\7z'
make = ''
isbase314 = False
silent_dep_builds = True
def host_info():
print('{0}Python setup{1}'.format(ANSI_CYAN, ANSI_RESET))
print(sys.version)
print('PYTHONPATH')
for dname in sys.path:
print(' ', dname)
print('platform =', distutils.util.get_platform())
print('{0}Available Visual Studio versions{1}'.format(ANSI_CYAN, ANSI_RESET))
for key in vcvars_table:
if os.path.exists(vcvars_table[key]):
print('Found', key, 'in', vcvars_table[key])
sys.stdout.flush()
# Used from unittests
def clear_lists():
del seen_setups[:]
del modules_to_compile[:]
setup.clear()
places.clear()
# Error-handler to make shutil.rmtree delete read-only files on Windows
def remove_readonly(func, path, excinfo):
os.chmod(path, stat.S_IWRITE)
func(path)
# source_set(setup)
#
# Source a settings file (extension .set) found in the setup_dirs path
# May be called recursively (from within a setup file)
def source_set(name):
# allowed separators: colon or whitespace
setup_dirs = os.getenv('SETUP_PATH', "").replace(':', ' ').split()
if len(setup_dirs) == 0:
raise NameError("{0}Search path for setup files (SETUP_PATH) is empty{1}".format(ANSI_RED,ANSI_RESET))
for set_dir in setup_dirs:
set_file = os.path.join(set_dir, name) + ".set"
if set_file in seen_setups:
print("Ignoring already included setup file {0}".format(set_file))
return
if os.path.isfile(set_file):
seen_setups.append(set_file)
print("Loading setup file {0}".format(set_file))
sys.stdout.flush()
with open(set_file) as fp:
for line in fp:
logger.debug('Next line: %s', line.strip())
if not line.strip() or line.strip()[0] == '#':
continue
if line.startswith("include"):
logger.debug('Found an include, reading %s', line.split()[1])
source_set(line.split()[1])
continue
assign = line.replace('"', '').strip().split("=", 1)
logger.debug('Interpreting as assignment')
setup.setdefault(assign[0], os.getenv(assign[0], ""))
if not setup[assign[0]].strip():
logger.debug('Doing assignment: %s = %s', assign[0], assign[1])
setup[assign[0]] = assign[1]
break
else:
raise NameError("{0}Setup file {1} does not exist in SETUP_PATH search path ({2}){3}"
.format(ANSI_RED, name, setup_dirs, ANSI_RESET))
# update_release_local(var, location)
# var name of the variable to set in RELEASE.local
# location location (absolute path) of where variable should point to
#
# Manipulate RELEASE.local in the cache location:
# - replace "$var=$location" line if it exists and has changed
# - otherwise add "$var=$location" line and possibly move EPICS_BASE=... line to the end
# Set places[var] = location
def update_release_local(var, location):
release_local = os.path.join(cachedir, 'RELEASE.local')
updated_line = '{0}={1}'.format(var, location.replace('\\', '/'))
places[var] = location
if not os.path.exists(release_local):
logger.debug('RELEASE.local does not exist, creating it')
try:
os.makedirs(cachedir)
except:
pass
fout = open(release_local, 'w')
fout.close()
base_line = ''
found = False
logger.debug("Opening RELEASE.local for adding '%s'", updated_line)
for line in fileinput.input(release_local, inplace=1):
outputline = line.strip()
if 'EPICS_BASE=' in line:
base_line = line.strip()
logger.debug("Found EPICS_BASE line '%s', not writing it", base_line)
continue
elif '{0}='.format(var) in line:
logger.debug("Found '%s=' line, replacing", var)
found = True
outputline = updated_line
logger.debug("Writing line to RELEASE.local: '%s'", outputline)
print(outputline)
fileinput.close()
fout = open(release_local,"a")
if not found:
logger.debug("Adding new definition: '%s'", updated_line)
print(updated_line, file=fout)
if base_line:
logger.debug("Writing EPICS_BASE line: '%s'", base_line)
print(base_line, file=fout)
fout.close()
def set_setup_from_env(dep):
for postf in ['', '_DIRNAME', '_REPONAME', '_REPOOWNER', '_REPOURL',
'_VARNAME', '_RECURSIVE', '_DEPTH', '_HOOK']:
if dep+postf in os.environ:
setup[dep+postf] = os.environ[dep+postf]
logger.debug('ENV assignment: %s = %s', dep+postf, setup[dep+postf])
def call_git(args, **kws):
if 'cwd' in kws:
place = kws['cwd']
else:
place = os.getcwd()
logger.debug("EXEC '%s' in %s", ' '.join(['git'] + args), place)
sys.stdout.flush()
exitcode = sp.call(['git'] + args, **kws)
logger.debug('EXEC DONE')
return exitcode
def call_make(args=[], **kws):
place = kws.get('cwd', os.getcwd())
parallel = kws.pop('parallel', 2)
silent = kws.pop('silent', False)
# no parallel make for Base 3.14
if parallel <= 0 or isbase314:
makeargs = []
else:
makeargs = ['-j{0}'.format(parallel), '-Otarget']
if silent:
makeargs += ['-s']
logger.debug("EXEC '%s' in %s", ' '.join([make] + makeargs + args), place)
sys.stdout.flush()
exitcode = sp.call([make] + makeargs + args, **kws)
logger.debug('EXEC DONE')
if exitcode != 0:
sys.exit(exitcode)
def get_git_hash(place):
logger.debug("EXEC 'git log -n1 --pretty=format:%%H' in %s", place)
sys.stdout.flush()
head = sp.check_output(['git', 'log', '-n1', '--pretty=format:%H'], cwd=place).decode()
logger.debug('EXEC DONE')
return head
def complete_setup(dep):
set_setup_from_env(dep)
setup.setdefault(dep, 'master')
setup.setdefault(dep+"_DIRNAME", dep.lower())
setup.setdefault(dep+"_REPONAME", dep.lower())
setup.setdefault('REPOOWNER', 'epics-modules')
setup.setdefault(dep+"_REPOOWNER", setup['REPOOWNER'])
setup.setdefault(dep+"_REPOURL", 'https://github.com/{0}/{1}.git'
.format(setup[dep+'_REPOOWNER'], setup[dep+'_REPONAME']))
setup.setdefault(dep+"_VARNAME", dep)
setup.setdefault(dep+"_RECURSIVE", 'YES')
setup.setdefault(dep+"_DEPTH", -1)
# add_dependency(dep, tag)
#
# Add a dependency to the cache area:
# - check out (recursive if configured) in the CACHE area unless it already exists and the
# required commit has been built
# - Defaults:
# $dep_DIRNAME = lower case ($dep)
# $dep_REPONAME = lower case ($dep)
# $dep_REPOURL = GitHub / $dep_REPOOWNER (or $REPOOWNER or epics-modules) / $dep_REPONAME .git
# $dep_VARNAME = $dep
# $dep_DEPTH = 5
# $dep_RECURSIVE = 1/YES (0/NO to for a flat clone)
# - Add $dep_VARNAME line to the RELEASE.local file in the cache area (unless already there)
# - Add full path to $modules_to_compile
def add_dependency(dep):
recurse = setup[dep+'_RECURSIVE'].lower()
if recurse not in ['0', 'no']:
recursearg = ["--recursive"]
elif recurse not in ['1', 'yes']:
recursearg = []
else:
raise RuntimeError("Invalid value for {}_RECURSIVE='{}' not 0/NO/1/YES".format(dep, recurse))
deptharg = {
'-1':['--depth', '5'],
'0':[],
}.get(str(setup[dep+'_DEPTH']), ['--depth', str(setup[dep+'_DEPTH'])])
tag = setup[dep]
logger.debug('Adding dependency %s with tag %s', dep, setup[dep])
# determine if dep points to a valid release or branch
if call_git(['ls-remote', '--quiet', '--exit-code', '--refs', setup[dep+'_REPOURL'], tag]):
raise RuntimeError("{0}{1} is neither a tag nor a branch name for {2} ({3}){4}"
.format(ANSI_RED, tag, dep, setup[dep+'_REPOURL'], ANSI_RESET))
dirname = setup[dep+'_DIRNAME']+'-{0}'.format(tag)
place = os.path.join(cachedir, dirname)
checked_file = os.path.join(place, "checked_out")
if os.path.isdir(place):
logger.debug('Dependency %s: directory %s exists, comparing checked-out commit', dep, place)
# check HEAD commit against the hash in marker file
if os.path.exists(checked_file):
with open(checked_file, 'r') as bfile:
checked_out = bfile.read().strip()
bfile.close()
else:
checked_out = 'never'
head = get_git_hash(place)
logger.debug('Found checked_out commit %s, git head is %s', checked_out, head)
if head != checked_out:
logger.debug('Dependency %s out of date - removing', dep)
shutil.rmtree(place, onerror=remove_readonly)
else:
print('Found {0} of dependency {1} up-to-date in {2}'.format(tag, dep, place))
sys.stdout.flush()
if not os.path.isdir(place):
if not os.path.isdir(cachedir):
os.makedirs(cachedir)
# clone dependency
print('Cloning {0} of dependency {1} into {2}'
.format(tag, dep, place))
sys.stdout.flush()
call_git(['clone', '--quiet'] + deptharg + recursearg + ['--branch', tag, setup[dep+'_REPOURL'], dirname], cwd=cachedir)
sp.check_call(['git', 'log', '-n1'], cwd=place)
modules_to_compile.append(place)
if dep == 'BASE':
# add MSI 1.7 to Base 3.14
versionfile = os.path.join(place, 'configure', 'CONFIG_BASE_VERSION')
if os.path.exists(versionfile):
with open(versionfile) as f:
if 'BASE_3_14=YES' in f.read():
print('Adding MSI 1.7 to {0}'.format(place))
sys.stdout.flush()
sp.check_call(['patch', '-p0', '-i', os.path.join(ciscriptsdir, 'add-msi-to-314.patch')],
cwd=place)
else:
# force including RELEASE.local for non-base modules by overwriting their configure/RELEASE
release = os.path.join(place, "configure", "RELEASE")
if os.path.exists(release):
with open(release, 'w') as fout:
print('-include $(TOP)/../RELEASE.local', file=fout)
# run hook if defined
if dep+'_HOOK' in setup:
hook = os.path.join(place, setup[dep+'_HOOK'])
if os.path.exists(hook):
print('Running hook {0} in {1}'.format(setup[dep+'_HOOK'], place))
sys.stdout.flush()
sp.check_call(hook, shell=True, cwd=place)
# write checked out commit hash to marker file
head = get_git_hash(place)
logger.debug('Writing hash of checked-out dependency (%s) to marker file', head)
with open(checked_file, "w") as fout:
print(head, file=fout)
fout.close()
update_release_local(setup[dep+"_VARNAME"], place)
def setup_for_build(args):
global make, isbase314
dllpaths = []
# there is no combined static and debug EPICS_HOST_ARCH target,
# so a combined debug and static target will appear to be just static
# but debug will have been specified in CONFIG_SITE by prepare()
hostarchsuffix=''
if re.search('debug', os.environ['CONFIGURATION']):
hostarchsuffix = '-debug'
if re.search('static', os.environ['CONFIGURATION']):
hostarchsuffix = '-static'
if os.environ['PLATFORM'].lower() == 'x86':
os.environ['EPICS_HOST_ARCH'] = 'win32-x86' + hostarchsuffix
elif os.environ['PLATFORM'].lower() == 'x64':
os.environ['EPICS_HOST_ARCH'] = 'windows-x64' + hostarchsuffix
if os.environ['CMP'] == 'vs2019':
# put strawberry perl in the PATH
os.environ['PATH'] = os.pathsep.join([os.path.join(r'C:\Strawberry\perl\site\bin'),
os.path.join(r'C:\Strawberry\perl\bin'),
os.environ['PATH']])
if os.environ['CMP'] == 'mingw':
if 'INCLUDE' not in os.environ:
os.environ['INCLUDE'] = ''
if os.environ['PLATFORM'].lower() == 'x86':
os.environ['EPICS_HOST_ARCH'] = 'win32-x86-mingw'
os.environ['INCLUDE'] = os.pathsep.join([r'C:\mingw-w64\i686-6.3.0-posix-dwarf-rt_v5-rev1\mingw32\include',
os.environ['INCLUDE']])
os.environ['PATH'] = os.pathsep.join([r'C:\mingw-w64\i686-6.3.0-posix-dwarf-rt_v5-rev1\mingw32\bin',
os.environ['PATH']])
elif os.environ['PLATFORM'].lower() == 'x64':
os.environ['EPICS_HOST_ARCH'] = 'windows-x64-mingw'
os.environ['INCLUDE'] = os.pathsep.join([r'C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\include',
os.environ['INCLUDE']])
os.environ['PATH'] = os.pathsep.join([r'C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin',
os.environ['PATH']])
make = os.path.join(toolsdir, 'make.exe')
base_place = '.'
if not building_base:
with open(os.path.join(cachedir, 'RELEASE.local'), 'r') as f:
lines = f.readlines()
for line in lines:
(mod, place) = line.strip().split('=')
bindir = os.path.join(place, 'bin', os.environ['EPICS_HOST_ARCH'])
if os.path.isdir(bindir):
dllpaths.append(bindir)
if mod == 'EPICS_BASE':
base_place = place
cfg_base_version = os.path.join(base_place, 'configure', 'CONFIG_BASE_VERSION')
if os.path.exists(cfg_base_version):
with open(cfg_base_version) as myfile:
if 'BASE_3_14=YES' in myfile.read():
isbase314 = True
bindir = os.path.join(os.getcwd(), 'bin', os.environ['EPICS_HOST_ARCH'])
if os.path.isdir(bindir):
dllpaths.append(bindir)
os.environ['PATH'] = os.pathsep.join(dllpaths + [os.environ['PATH']])
# apparently %CD% is handled automagically
os.environ['TOP'] = os.getcwd()
addpaths = []
for path in args.paths:
try:
addpaths.append(path.format(**os.environ))
except KeyError:
print('Environment')
[print(' ',K,'=',repr(V)) for K,V in os.environ.items()]
raise
os.environ['PATH'] = os.pathsep.join([os.environ['PATH']] + addpaths)
def prepare(args):
host_info()
print('{0}Loading setup files{1}'.format(ANSI_YELLOW, ANSI_RESET))
source_set('defaults')
if 'SET' in os.environ:
source_set(os.environ['SET'])
[complete_setup(mod) for mod in modlist()]
logger.debug('Loaded setup')
kvs = list(setup.items())
kvs.sort()
[logger.debug(' %s = "%s"', *kv) for kv in kvs]
# we're working with tags (detached heads) a lot: suppress advice
call_git(['config', '--global', 'advice.detachedHead', 'false'])
print('{0}Checking/cloning dependencies{1}'.format(ANSI_YELLOW, ANSI_RESET))
sys.stdout.flush()
[add_dependency(mod) for mod in modlist()]
if not building_base:
if os.path.isdir('configure'):
targetdir = 'configure'
else:
targetdir = '.'
shutil.copy(os.path.join(cachedir, 'RELEASE.local'), targetdir)
print('{0}Configuring EPICS build system{1}'.format(ANSI_YELLOW, ANSI_RESET))
with open(os.path.join(places['EPICS_BASE'], 'configure', 'CONFIG_SITE'), 'a') as config_site:
if re.search('static', os.environ['CONFIGURATION']):
config_site.write('SHARED_LIBRARIES=NO\n')
config_site.write('STATIC_BUILD=YES\n')
linktype = 'static'
else:
linktype = 'dynamic (DLL)'
if re.search('debug', os.environ['CONFIGURATION']):
config_site.write('HOST_OPT=NO\n')
optitype = 'debug'
else:
optitype = 'optimized'
print('EPICS Base build system set up for {0} build with {1} linking'
.format(optitype, linktype))
if not os.path.isdir(toolsdir):
os.makedirs(toolsdir)
makever = '4.2.1'
if not os.path.exists(os.path.join(toolsdir, 'make.exe')):
print('Installing Make 4.2.1 from ANL web site')
sys.stdout.flush()
sp.check_call(['curl', '-fsS', '--retry', '3', '-o', 'make-{0}.zip'.format(makever),
'https://epics.anl.gov/download/tools/make-{0}-win64.zip'.format(makever)],
cwd=toolsdir)
sp.check_call([zip7, 'e', 'make-{0}.zip'.format(makever)], cwd=toolsdir)
os.remove(os.path.join(toolsdir, 'make-{0}.zip'.format(makever)))
setup_for_build(args)
print('{0}EPICS_HOST_ARCH = {1}{2}'.format(ANSI_CYAN, os.environ['EPICS_HOST_ARCH'], ANSI_RESET))
print('{0}$ {1} --version{2}'.format(ANSI_CYAN, make, ANSI_RESET))
sys.stdout.flush()
call_make(['--version'], parallel=0)
print('{0}$ perl --version{1}'.format(ANSI_CYAN, ANSI_RESET))
sys.stdout.flush()
sp.check_call(['perl', '--version'])
if os.environ['CMP'] == 'mingw':
print('{0}$ gcc --version{1}'.format(ANSI_CYAN, ANSI_RESET))
sys.stdout.flush()
sp.check_call(['gcc', '--version'])
else:
print('{0}$ cl{1}'.format(ANSI_CYAN, ANSI_RESET))
sys.stdout.flush()
sp.check_call(['cl'])
if not building_base:
for mod in modlist():
place = places[setup[mod+"_VARNAME"]]
print('{0}Building dependency {1} in {2}{3}'.format(ANSI_YELLOW, mod, place, ANSI_RESET))
call_make(cwd=place, silent=silent_dep_builds)
print('{0}Dependency module information{1}'.format(ANSI_CYAN, ANSI_RESET))
print('Module Tag Binaries Commit')
print(100 * '-')
for mod in modlist():
commit = sp.check_output(['git', 'log', '-n1', '--oneline'], cwd=places[setup[mod+"_VARNAME"]]).strip()
print("%-10s %-12s %-11s %s" % (mod, setup[mod], 'rebuilt', commit))
print('{0}Contents of RELEASE.local{1}'.format(ANSI_CYAN, ANSI_RESET))
with open(os.path.join(cachedir, 'RELEASE.local'), 'r') as f:
print(f.read().strip())
def build(args):
setup_for_build(args)
print('{0}Building the main module{1}'.format(ANSI_YELLOW, ANSI_RESET))
call_make(args.makeargs)
def test(args):
setup_for_build(args)
print('{0}Running the main module tests{1}'.format(ANSI_YELLOW, ANSI_RESET))
call_make(['tapfiles'], parallel=0)
call_make(['test-results'], parallel=0, silent=True)
def doExec(args):
'exec user command with vcvars'
setup_for_build(args)
os.environ['MAKE'] = make
print('Execute command {}'.format(args.cmd))
sys.stdout.flush()
sp.check_call(' '.join(args.cmd), shell=True)
def with_vcvars(cmd):
'''re-exec main script with a (hopefully different) command
'''
CC = os.environ['CMP']
# cf. https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line
info = {
'python': sys.executable,
'self': sys.argv[0],
'cmd':cmd,
}
info['arch'] = {
'x86': 'x86', # 'amd64_x86' ??
'x64': 'amd64',
}[os.environ['PLATFORM'].lower()] # 'x86' or 'x64'
info['vcvars'] = vcvars_table[CC]
script='''
call "{vcvars}" {arch}
"{python}" "{self}" {cmd}
'''.format(**info)
logger.debug('----- Creating vcvars-trampoline.bat -----')
for line in script.split('\n'):
logger.debug(line)
logger.debug('----- snip -----')
with open('vcvars-trampoline.bat', 'w') as F:
F.write(script)
print('{0}Calling vcvars-trampoline.bat to set environment for {1} on {2}{3}'
.format(ANSI_YELLOW, CC, os.environ['PLATFORM'], ANSI_RESET))
sys.stdout.flush()
returncode = sp.call('vcvars-trampoline.bat', shell=True)
if returncode != 0:
sys.exit(returncode)
def getargs():
from argparse import ArgumentParser, REMAINDER
P = ArgumentParser()
P.add_argument('--no-vcvars', dest='vcvars', default=True, action='store_false',
help='Assume vcvarsall.bat has already been run')
P.add_argument('--add-path', dest='paths', default=[], action='append',
help='Append directory to %PATH%. Expands {ENVVAR}')
SP = P.add_subparsers()
CMD = SP.add_parser('prepare')
CMD.set_defaults(func=prepare)
CMD = SP.add_parser('build')
CMD.add_argument('makeargs', nargs=REMAINDER)
CMD.set_defaults(func=build)
CMD = SP.add_parser('test')
CMD.set_defaults(func=test)
CMD = SP.add_parser('exec')
CMD.add_argument('cmd', nargs=REMAINDER)
CMD.set_defaults(func=doExec)
return P
def main(raw):
global silent_dep_builds
args = getargs().parse_args(raw)
if 'VV' in os.environ and os.environ['VV'] == '1':
logging.basicConfig(level=logging.DEBUG)
silent_dep_builds = False
if args.vcvars and os.environ['CMP'].startswith('vs'):
# re-exec with MSVC in PATH
with_vcvars(' '.join(['--no-vcvars']+raw))
else:
args.func(args)
if __name__=='__main__':
main(sys.argv[1:])

View File

@@ -1,890 +0,0 @@
#!/usr/bin/env python
"""Module ci-scripts unit tests
"""
# SET=test00 in the environment and run the tests in this script
# all other jobs are started as compile jobs
from __future__ import print_function
import sys, os, shutil, fileinput
import distutils.util
import re
import subprocess as sp
import unittest
import logging
from argparse import Namespace
builddir = os.getcwd()
# Detect basic context (service, os)
if 'TRAVIS' in os.environ:
ci_service = 'travis'
ci_os = os.environ['TRAVIS_OS_NAME']
if 'APPVEYOR' in os.environ:
ci_service = 'appveyor'
if re.match(r'^Visual', os.environ['APPVEYOR_BUILD_WORKER_IMAGE']):
ci_os = 'windows'
elif re.match(r'^Ubuntu', os.environ['APPVEYOR_BUILD_WORKER_IMAGE']):
ci_os = 'linux'
elif re.match(r'^macOS', os.environ['APPVEYOR_BUILD_WORKER_IMAGE']):
ci_os = 'osx'
if 'GITHUB_ACTIONS' in os.environ:
ci_service = 'github-actions'
if os.environ['RUNNER_OS'] == 'macOS':
ci_os = 'osx'
else:
ci_os = os.environ['RUNNER_OS'].lower()
def find_in_file(regex, filename):
file = open(filename, "r")
for line in file:
if re.search(regex, line):
return True
return False
def getStringIO():
if sys.version_info > (3, 0):
import io
return io.StringIO()
else:
import StringIO
return StringIO.StringIO()
sys.path.append('.')
import cue
# we're working with tags (detached heads) a lot: suppress advice
cue.call_git(['config', '--global', 'advice.detachedHead', 'false'])
class TestSourceSet(unittest.TestCase):
def setUp(self):
os.environ['SETUP_PATH'] = '.:appveyor'
if 'BASE' in os.environ:
del os.environ['BASE']
cue.clear_lists()
os.chdir(builddir)
def test_EmptySetupDirsPath(self):
del os.environ['SETUP_PATH']
self.assertRaisesRegexp(NameError, '\(SETUP_PATH\) is empty', cue.source_set, 'test01')
def test_InvalidSetupName(self):
self.assertRaisesRegexp(NameError, 'does not exist in SETUP_PATH', cue.source_set, 'xxdoesnotexistxx')
def test_ValidSetupName(self):
capturedOutput = getStringIO()
sys.stdout = capturedOutput
cue.source_set('test01')
sys.stdout = sys.__stdout__
self.assertEqual(cue.setup['BASE'], '7.0', 'BASE was not set to \'7.0\'')
def test_SetupDoesNotOverridePreset(self):
os.environ['BASE'] = 'foo'
capturedOutput = getStringIO()
sys.stdout = capturedOutput
cue.source_set('test01')
sys.stdout = sys.__stdout__
self.assertEqual(cue.setup['BASE'], 'foo',
'Preset BASE was overridden by test01 setup (expected \'foo\' got {0})'
.format(cue.setup['BASE']))
def test_IncludeSetupFirstSetWins(self):
captured_output = getStringIO()
sys.stdout = captured_output
cue.source_set('test02')
sys.stdout = sys.__stdout__
self.assertEqual(cue.setup['BASE'], 'foo',
'BASE set in test02 was overridden by test01 setup (expected \'foo\' got {0})'
.format(cue.setup['BASE']))
self.assertEqual(cue.setup['FOO'], 'bar', 'Setting of single word does not work')
self.assertEqual(cue.setup['FOO2'], 'bar bar2', 'Setting of multiple words does not work')
self.assertEqual(cue.setup['FOO3'], 'bar bar2', 'Indented setting of multiple words does not work')
self.assertEqual(cue.setup['SNCSEQ'], 'R2-2-8', 'Setup test01 was not included')
def test_DoubleIncludeGetsIgnored(self):
capturedOutput = getStringIO()
sys.stdout = capturedOutput
cue.source_set('test03')
sys.stdout = sys.__stdout__
self.assertRegexpMatches(capturedOutput.getvalue(), 'Ignoring already included setup file')
class TestUpdateReleaseLocal(unittest.TestCase):
release_local = os.path.join(cue.cachedir, 'RELEASE.local')
def setUp(self):
if os.path.exists(self.release_local):
os.remove(self.release_local)
os.chdir(builddir)
def test_SetModule(self):
cue.update_release_local('MOD1', '/foo/bar')
found = 0
for line in fileinput.input(self.release_local, inplace=1):
if 'MOD1=' in line:
self.assertEqual(line.strip(), 'MOD1=/foo/bar', 'MOD1 not set correctly')
found += 1
fileinput.close()
self.assertEqual(found, 1, 'MOD1 not written once to RELEASE.local (found {0})'.format(found))
def test_SetBaseAndMultipleModules(self):
cue.update_release_local('EPICS_BASE', '/bar/foo')
cue.update_release_local('MOD1', '/foo/bar')
cue.update_release_local('MOD2', '/foo/bar2')
cue.update_release_local('MOD1', '/foo/bar1')
found = {}
foundat = {}
for line in fileinput.input(self.release_local, inplace=1):
if 'MOD1=' in line:
self.assertEqual(line.strip(), 'MOD1=/foo/bar1',
'MOD1 not set correctly (expected \'MOD1=/foo/bar1\' found \'{0}\')'
.format(line))
if 'mod1' in found:
found['mod1'] += 1
else:
found['mod1'] = 1
foundat['mod1'] = fileinput.filelineno()
if 'MOD2=' in line:
self.assertEqual(line.strip(), 'MOD2=/foo/bar2',
'MOD2 not set correctly (expected \'MOD2=/foo/bar2\' found \'{0}\')'
.format(line))
if 'mod2' in found:
found['mod2'] += 1
else:
found['mod2'] = 1
foundat['mod2'] = fileinput.filelineno()
if 'EPICS_BASE=' in line:
self.assertEqual(line.strip(), 'EPICS_BASE=/bar/foo',
'EPICS_BASE not set correctly (expected \'EPICS_BASE=/bar/foo\' found \'{0}\')'
.format(line))
if 'base' in found:
found['base'] += 1
else:
found['base'] = 1
foundat['base'] = fileinput.filelineno()
fileinput.close()
self.assertEqual(found['mod1'], 1,
'MOD1 does not appear once in RELEASE.local (found {0})'.format(found['mod1']))
self.assertEqual(found['mod2'], 1,
'MOD2 does not appear once in RELEASE.local (found {0})'.format(found['mod2']))
self.assertEqual(found['base'], 1,
'EPICS_BASE does not appear once in RELEASE.local (found {0})'.format(found['base']))
self.assertGreater(foundat['base'], foundat['mod2'],
'EPICS_BASE (line {0}) appears before MOD2 (line {1})'
.format(foundat['base'], foundat['mod2']))
self.assertGreater(foundat['mod2'], foundat['mod1'],
'MOD2 (line {0}) appears before MOD1 (line {1})'.format(foundat['mod2'], foundat['mod1']))
class TestAddDependencyUpToDateCheck(unittest.TestCase):
hash_3_15_6 = "ce7943fb44beb22b453ddcc0bda5398fadf72096"
location = os.path.join(cue.cachedir, 'base-R3.15.6')
licensefile = os.path.join(location, 'LICENSE')
checked_file = os.path.join(location, 'checked_out')
release_file = os.path.join(location, 'configure', 'RELEASE')
def setUp(self):
os.environ['SETUP_PATH'] = '.:appveyor'
if os.path.exists(self.location):
shutil.rmtree(self.location, onerror=cue.remove_readonly)
cue.clear_lists()
os.chdir(builddir)
cue.source_set('defaults')
cue.complete_setup('BASE')
def test_MissingDependency(self):
cue.setup['BASE'] = 'R3.15.6'
cue.add_dependency('BASE')
self.assertTrue(os.path.exists(self.licensefile), 'Missing dependency was not checked out')
self.assertTrue(os.path.exists(self.checked_file), 'Checked-out commit marker was not written')
with open(self.checked_file, 'r') as bfile:
checked_out = bfile.read().strip()
bfile.close()
self.assertEqual(checked_out, self.hash_3_15_6,
'Wrong commit of dependency checked out (expected=\"{0}\" found=\"{1}\")'
.format(self.hash_3_15_6, checked_out))
self.assertFalse(find_in_file('include \$\(TOP\)/../RELEASE.local', self.release_file),
'RELEASE in Base includes TOP/../RELEASE.local')
def test_UpToDateDependency(self):
cue.setup['BASE'] = 'R3.15.6'
cue.add_dependency('BASE')
os.remove(self.licensefile)
cue.add_dependency('BASE')
self.assertFalse(os.path.exists(self.licensefile), 'Check out on top of existing up-to-date dependency')
def test_OutdatedDependency(self):
cue.setup['BASE'] = 'R3.15.6'
cue.add_dependency('BASE')
os.remove(self.licensefile)
with open(self.checked_file, "w") as fout:
print('XXX not the right hash XXX', file=fout)
fout.close()
cue.add_dependency('BASE')
self.assertTrue(os.path.exists(self.licensefile), 'No check-out on top of out-of-date dependency')
with open(self.checked_file, 'r') as bfile:
checked_out = bfile.read().strip()
bfile.close()
self.assertEqual(checked_out, self.hash_3_15_6,
"Wrong commit of dependency checked out (expected='{0}' found='{1}')"
.format(self.hash_3_15_6, checked_out))
def is_shallow_repo(place):
check = sp.check_output(['git', 'rev-parse', '--is-shallow-repository'], cwd=place).strip().decode('ascii')
if check == '--is-shallow-repository':
if os.path.exists(os.path.join(place, '.git', 'shallow')):
check = 'true'
else:
check = 'false'
return check == 'true'
class TestAddDependencyOptions(unittest.TestCase):
location = os.path.join(cue.cachedir, 'mcoreutils-master')
testfile = os.path.join(location, '.ci', 'LICENSE')
def setUp(self):
os.environ['SETUP_PATH'] = '.'
if os.path.exists(cue.cachedir):
shutil.rmtree(cue.cachedir, onerror=cue.remove_readonly)
cue.clear_lists()
cue.detect_context()
cue.source_set('defaults')
cue.complete_setup('MCoreUtils')
cue.setup['MCoreUtils'] = 'master'
def test_Default(self):
cue.add_dependency('MCoreUtils')
self.assertTrue(os.path.exists(self.testfile),
'Submodule (.ci) not checked out recursively (requested: default=YES')
self.assertTrue(is_shallow_repo(self.location),
'Module not checked out shallow (requested: default=5)')
def test_SetRecursiveNo(self):
cue.setup['MCoreUtils_RECURSIVE'] = 'NO'
cue.add_dependency('MCoreUtils')
self.assertFalse(os.path.exists(self.testfile), 'Submodule (.ci) checked out recursively')
def test_SetDepthZero(self):
cue.setup['MCoreUtils_DEPTH'] = '0'
cue.add_dependency('MCoreUtils')
self.assertFalse(is_shallow_repo(self.location), 'Module checked out shallow (requested full)')
def test_SetDepthThree(self):
cue.setup['MCoreUtils_DEPTH'] = '3'
cue.add_dependency('MCoreUtils')
self.assertTrue(is_shallow_repo(self.location),
'Module not checked out shallow (requested: depth=3)')
def test_AddMsiTo314(self):
cue.complete_setup('BASE')
cue.setup['BASE'] = 'R3.14.12.1'
msifile = os.path.join(cue.cachedir, 'base-R3.14.12.1', 'src', 'dbtools', 'msi.c')
cue.add_dependency('BASE')
self.assertTrue(os.path.exists(msifile), 'MSI was not added to Base 3.14')
def test_DefaultBaseBranch(self):
cue.complete_setup('BASE')
self.assertEqual(cue.setup['BASE'], '7.0',
'Default Base branch is not 7.0 (found {0})'.format(cue.setup['BASE']))
def repo_access(dep):
cue.set_setup_from_env(dep)
cue.setup.setdefault(dep + "_DIRNAME", dep.lower())
cue.setup.setdefault(dep + "_REPONAME", dep.lower())
cue.setup.setdefault('REPOOWNER', 'epics-modules')
cue.setup.setdefault(dep + "_REPOOWNER", cue.setup['REPOOWNER'])
cue.setup.setdefault(dep + "_REPOURL", 'https://github.com/{0}/{1}.git'
.format(cue.setup[dep + '_REPOOWNER'], cue.setup[dep + '_REPONAME']))
with open(os.devnull, 'w') as devnull:
return cue.call_git(['ls-remote', '--quiet', '--heads', cue.setup[dep + '_REPOURL']],
stdout=devnull, stderr=devnull)
class TestDefaultModuleURLs(unittest.TestCase):
modules = ['BASE', 'PVDATA', 'PVACCESS', 'NTYPES',
'SNCSEQ', 'STREAM', 'ASYN', 'STD',
'CALC', 'AUTOSAVE', 'BUSY', 'SSCAN',
'IOCSTATS', 'MOTOR', 'IPAC', ]
def setUp(self):
os.environ['SETUP_PATH'] = '.:appveyor'
cue.clear_lists()
os.chdir(builddir)
cue.source_set('defaults')
def test_Repos(self):
for mod in self.modules:
self.assertEqual(repo_access(mod), 0, 'Defaults for {0} do not point to a valid git repository at {1}'
.format(mod, cue.setup[mod + '_REPOURL']))
@unittest.skipIf(ci_os != 'windows', 'VCVars test only applies to windows')
class TestVCVars(unittest.TestCase):
def test_vcvars(self):
if ci_service == 'travis':
os.environ['TRAVIS_COMPILER'] = 'vs2017'
else:
os.environ['CONFIGURATION'] = 'default'
if ci_service == 'github-actions' and os.environ['IMAGEOS'] == 'win16':
os.environ['CMP'] = 'vs2017'
else:
os.environ['CMP'] = 'vs2019'
cue.detect_context()
cue.with_vcvars('env')
@unittest.skipIf(ci_service != 'travis', 'Run travis tests only on travis')
class TestTravisDetectContext(unittest.TestCase):
def setUp(self):
os.environ['TRAVIS'] = 'true'
os.environ['TRAVIS_OS_NAME'] = 'linux'
os.environ['TRAVIS_COMPILER'] = 'gcc'
def tearDown(self):
cue.clear_lists()
os.environ.pop('BCFG', None)
os.environ.pop('TEST', None)
os.environ.pop('STATIC', None)
def test_LinuxGccNone(self):
cue.detect_context()
self.assertEqual(cue.ci['service'], 'travis', "ci['service'] is {0} (expected: travis)"
.format(cue.ci['service']))
self.assertEqual(cue.ci['os'], 'linux', "ci['os'] is {0} (expected: linux)"
.format(cue.ci['os']))
self.assertEqual(cue.ci['compiler'], 'gcc', "ci['compiler'] is {0} (expected: gcc)"
.format(cue.ci['compiler']))
self.assertEqual(cue.ci['platform'], 'x64', "ci['platform'] is {0} (expected: x64)"
.format(cue.ci['platform']))
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
def test_LinuxClangNone(self):
os.environ['TRAVIS_COMPILER'] = 'clang'
cue.detect_context()
self.assertEqual(cue.ci['service'], 'travis', "ci['service'] is {0} (expected: travis)"
.format(cue.ci['service']))
self.assertEqual(cue.ci['os'], 'linux', "ci['os'] is {0} (expected: linux)"
.format(cue.ci['os']))
self.assertEqual(cue.ci['compiler'], 'clang', "ci['compiler'] is {0} (expected: clang)"
.format(cue.ci['compiler']))
self.assertEqual(cue.ci['platform'], 'x64', "ci['platform'] is {0} (expected: x64)"
.format(cue.ci['platform']))
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
def test_BcfgShared(self):
os.environ['BCFG'] = 'shared'
cue.detect_context()
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
def test_BcfgStatic(self):
os.environ['BCFG'] = 'static'
cue.detect_context()
self.assertTrue(cue.ci['static'], "ci['static'] is False (expected: True)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'static-optimized',
"ci['configuration'] is {0} (expected: static-optimized)"
.format(cue.ci['configuration']))
def test_BcfgDebug(self):
os.environ['BCFG'] = 'debug'
cue.detect_context()
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertTrue(cue.ci['debug'], "ci['debug'] is False (expected: True)")
self.assertEqual(cue.ci['configuration'], 'shared-debug',
"ci['configuration'] is {0} (expected: shared-debug)"
.format(cue.ci['configuration']))
def test_BcfgStaticDebug(self):
os.environ['BCFG'] = 'static-debug'
cue.detect_context()
self.assertTrue(cue.ci['static'], "ci['static'] is False (expected: True)")
self.assertTrue(cue.ci['debug'], "ci['debug'] is False (expected: True)")
self.assertEqual(cue.ci['configuration'], 'static-debug',
"ci['configuration'] is {0} (expected: static-debug)"
.format(cue.ci['configuration']))
def test_TestNo(self):
os.environ['TEST'] = 'NO'
cue.detect_context()
self.assertFalse(cue.ci['test'], "ci['test'] is True (expected: False)")
def test_WindowsGccNone(self):
os.environ['TRAVIS_OS_NAME'] = 'windows'
cue.detect_context()
self.assertEqual(cue.ci['service'], 'travis', "ci['service'] is {0} (expected: travis)"
.format(cue.ci['service']))
self.assertEqual(cue.ci['os'], 'windows', "ci['os'] is {0} (expected: windows)"
.format(cue.ci['os']))
self.assertEqual(cue.ci['compiler'], 'gcc', "ci['compiler'] is {0} (expected: gcc)"
.format(cue.ci['compiler']))
self.assertEqual(cue.ci['platform'], 'x64', "ci['platform'] is {0} (expected: x64)"
.format(cue.ci['platform']))
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
self.assertIn('strawberryperl', cue.ci['choco'], "'strawberryperl' is not in ci['choco']")
self.assertIn('make', cue.ci['choco'], "'make' is not in ci['choco']")
def test_WindowsVs2017None(self):
os.environ['TRAVIS_OS_NAME'] = 'windows'
os.environ['TRAVIS_COMPILER'] = 'vs2017'
cue.detect_context()
self.assertEqual(cue.ci['service'], 'travis', "ci['service'] is {0} (expected: travis)"
.format(cue.ci['service']))
self.assertEqual(cue.ci['os'], 'windows', "ci['os'] is {0} (expected: windows)"
.format(cue.ci['os']))
self.assertEqual(cue.ci['compiler'], 'vs2017', "ci['compiler'] is {0} (expected: vs2017)"
.format(cue.ci['compiler']))
self.assertEqual(cue.ci['platform'], 'x64', "ci['platform'] is {0} (expected: x64)"
.format(cue.ci['platform']))
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
self.assertIn('strawberryperl', cue.ci['choco'], "'strawberryperl' is not in ci['choco']")
self.assertIn('make', cue.ci['choco'], "'make' is not in ci['choco']")
def test_WindowsVs2019None(self):
os.environ['TRAVIS_OS_NAME'] = 'windows'
os.environ['TRAVIS_COMPILER'] = 'vs2019'
cue.detect_context()
self.assertEqual(cue.ci['service'], 'travis', "ci['service'] is {0} (expected: travis)"
.format(cue.ci['service']))
self.assertEqual(cue.ci['os'], 'windows', "ci['os'] is {0} (expected: windows)"
.format(cue.ci['os']))
self.assertEqual(cue.ci['compiler'], 'vs2017', "ci['compiler'] is {0} (expected: vs2017)"
.format(cue.ci['compiler']))
self.assertEqual(cue.ci['platform'], 'x64', "ci['platform'] is {0} (expected: x64)"
.format(cue.ci['platform']))
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
self.assertIn('strawberryperl', cue.ci['choco'], "'strawberryperl' is not in ci['choco']")
self.assertIn('make', cue.ci['choco'], "'make' is not in ci['choco']")
def test_OsxClangNone(self):
os.environ['TRAVIS_OS_NAME'] = 'osx'
os.environ['TRAVIS_COMPILER'] = 'clang'
cue.detect_context()
self.assertEqual(cue.ci['service'], 'travis', "ci['service'] is {0} (expected: travis)"
.format(cue.ci['service']))
self.assertEqual(cue.ci['os'], 'osx', "ci['os'] is {0} (expected: osx)"
.format(cue.ci['os']))
self.assertEqual(cue.ci['compiler'], 'clang', "ci['compiler'] is {0} (expected: clang)"
.format(cue.ci['compiler']))
self.assertEqual(cue.ci['platform'], 'x64', "ci['platform'] is {0} (expected: x64)"
.format(cue.ci['platform']))
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
def test_StaticGetsWarning(self):
os.environ['STATIC'] = 'YES'
capturedOutput = getStringIO()
sys.stdout = capturedOutput
cue.detect_context()
sys.stdout = sys.__stdout__
self.assertRegexpMatches(capturedOutput.getvalue(), "Variable 'STATIC' not supported anymore")
def test_MisspelledBcfgGetsWarning(self):
os.environ['BCFG'] = 'static-dubug'
capturedOutput = getStringIO()
sys.stdout = capturedOutput
cue.detect_context()
sys.stdout = sys.__stdout__
self.assertRegexpMatches(capturedOutput.getvalue(), "Unrecognized build configuration setting")
@unittest.skipIf(ci_service != 'appveyor', 'Run appveyor tests only on appveyor')
class TestAppveyorDetectContext(unittest.TestCase):
def setUp(self):
os.environ['APPVEYOR'] = 'True'
os.environ['APPVEYOR_BUILD_WORKER_IMAGE'] = 'Visual Studio 2019'
os.environ['CMP'] = 'vs2019'
os.environ['CONFIGURATION'] = 'default'
os.environ['PLATFORM'] = 'x64'
def tearDown(self):
cue.clear_lists()
os.environ.pop('STATIC', None)
os.environ.pop('TEST', None)
def test_Platform32(self):
os.environ['PLATFORM'] = 'x86'
cue.detect_context()
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
self.assertEqual(cue.ci['platform'], 'x86',
"ci['platform'] is {0} (expected: x86)"
.format(cue.ci['platform']))
def test_Platform64(self):
cue.detect_context()
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
self.assertEqual(cue.ci['platform'], 'x64',
"ci['platform'] is {0} (expected: x64)"
.format(cue.ci['platform']))
def test_PlatformX64(self):
os.environ['PLATFORM'] = 'X64'
cue.detect_context()
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
self.assertEqual(cue.ci['platform'], 'x64',
"ci['platform'] is {0} (expected: x64)"
.format(cue.ci['platform']))
def test_ConfigDefault(self):
cue.detect_context()
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
def test_ConfigStatic(self):
os.environ['CONFIGURATION'] = 'static'
cue.detect_context()
self.assertTrue(cue.ci['static'], "ci['static'] is False (expected: True)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'static-optimized',
"ci['configuration'] is {0} (expected: static-optimized)"
.format(cue.ci['configuration']))
def test_ConfigDebug(self):
os.environ['CONFIGURATION'] = 'debug'
cue.detect_context()
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertTrue(cue.ci['debug'], "ci['debug'] is False (expected: True)")
self.assertEqual(cue.ci['configuration'], 'shared-debug',
"ci['configuration'] is {0} (expected: shared-debug)"
.format(cue.ci['configuration']))
def test_ConfigStaticDebug(self):
os.environ['CONFIGURATION'] = 'static-debug'
cue.detect_context()
self.assertTrue(cue.ci['static'], "ci['static'] is False (expected: True)")
self.assertTrue(cue.ci['debug'], "ci['debug'] is False (expected: True)")
self.assertEqual(cue.ci['configuration'], 'static-debug',
"ci['configuration'] is {0} (expected: static-debug)"
.format(cue.ci['configuration']))
def test_TestNo(self):
os.environ['TEST'] = 'NO'
cue.detect_context()
self.assertFalse(cue.ci['test'], "ci['test'] is True (expected: False)")
def test_WindowsGccNone(self):
os.environ['CMP'] = 'gcc'
cue.detect_context()
self.assertEqual(cue.ci['service'], 'appveyor', "ci['service'] is {0} (expected: appveyor)"
.format(cue.ci['service']))
self.assertEqual(cue.ci['os'], 'windows', "ci['os'] is {0} (expected: windows)"
.format(cue.ci['os']))
self.assertEqual(cue.ci['compiler'], 'gcc', "ci['compiler'] is {0} (expected: gcc)"
.format(cue.ci['compiler']))
self.assertEqual(cue.ci['platform'], 'x64', "ci['platform'] is {0} (expected: x64)"
.format(cue.ci['platform']))
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
self.assertIn('make', cue.ci['choco'], "'make' is not in ci['choco']")
def test_WindowsVs2017None(self):
os.environ['APPVEYOR_BUILD_WORKER_IMAGE'] = 'Visual Studio 2017'
os.environ['CMP'] = 'vs2017'
os.environ['PLATFORM'] = 'x86'
cue.detect_context()
self.assertEqual(cue.ci['service'], 'appveyor', "ci['service'] is {0} (expected: appveyor)"
.format(cue.ci['service']))
self.assertEqual(cue.ci['os'], 'windows', "ci['os'] is {0} (expected: windows)"
.format(cue.ci['os']))
self.assertEqual(cue.ci['compiler'], 'vs2017', "ci['compiler'] is {0} (expected: vs2017)"
.format(cue.ci['compiler']))
self.assertEqual(cue.ci['platform'], 'x86', "ci['platform'] is {0} (expected: x86)"
.format(cue.ci['platform']))
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
self.assertIn('make', cue.ci['choco'], "'make' is not in ci['choco']")
def test_WindowsVs2019None(self):
cue.detect_context()
self.assertEqual(cue.ci['service'], 'appveyor', "ci['service'] is {0} (expected: appveyor)"
.format(cue.ci['service']))
self.assertEqual(cue.ci['os'], 'windows', "ci['os'] is {0} (expected: windows)"
.format(cue.ci['os']))
self.assertEqual(cue.ci['compiler'], 'vs2019', "ci['compiler'] is {0} (expected: vs2019)"
.format(cue.ci['compiler']))
self.assertEqual(cue.ci['platform'], 'x64', "ci['platform'] is {0} (expected: x64)"
.format(cue.ci['platform']))
self.assertFalse(cue.ci['static'], "ci['static'] is True (expected: False)")
self.assertFalse(cue.ci['debug'], "ci['debug'] is True (expected: False)")
self.assertEqual(cue.ci['configuration'], 'shared-optimized',
"ci['configuration'] is {0} (expected: shared-optimized)"
.format(cue.ci['configuration']))
self.assertIn('make', cue.ci['choco'], "'make' is not in ci['choco']")
def test_StaticGetsWarning(self):
os.environ['STATIC'] = 'YES'
capturedOutput = getStringIO()
sys.stdout = capturedOutput
cue.detect_context()
sys.stdout = sys.__stdout__
self.assertRegexpMatches(capturedOutput.getvalue(), "Variable 'STATIC' not supported anymore")
def test_MisspelledConfigurationGetsWarning(self):
os.environ['CONFIGURATION'] = 'static-dubug'
capturedOutput = getStringIO()
sys.stdout = capturedOutput
cue.detect_context()
sys.stdout = sys.__stdout__
self.assertRegexpMatches(capturedOutput.getvalue(), "Unrecognized build configuration setting")
class TestSetupForBuild(unittest.TestCase):
args = Namespace(paths=[])
cue.building_base = True
if ci_os == 'windows':
sp.check_call(['choco', 'install', 'make', 'strawberryperl', '-ry'])
def setUp(self):
if ci_service == 'appveyor':
os.environ['CONFIGURATION'] = 'default'
cue.detect_context()
def tearDown(self):
os.environ.pop('EPICS_HOST_ARCH', None)
cue.clear_lists()
def test_AddPathsOption(self):
os.environ['FOOBAR'] = 'BAR'
args = Namespace(paths=['/my/{FOOBAR}/dir', '/my/foobar'])
cue.setup_for_build(args)
self.assertTrue(re.search('/my/BAR/dir', os.environ['PATH']), 'Expanded path not in PATH')
self.assertTrue(re.search('/foobar', os.environ['PATH']), 'Plain path not in PATH')
os.environ.pop('FOOBAR', None)
@unittest.skipIf(ci_os != 'windows', 'HostArchConfiguration test only applies to windows')
def test_HostArchConfiguration(self):
cue.ci['compiler'] = 'vs2017'
for cue.ci['debug'] in [True, False]:
for cue.ci['static'] in [True, False]:
config_st = {True: 'static', False: 'shared'}
config_db = {True: '-debug', False: '-optimized'}
config = config_st[cue.ci['static']] + config_db[cue.ci['debug']]
cue.setup_for_build(self.args)
self.assertTrue('EPICS_HOST_ARCH' in os.environ,
'EPICS_HOST_ARCH is not set for Configuration={0}'.format(config))
if cue.ci['static']:
self.assertTrue(re.search('-static$', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is not -static for Configuration={0}'.format(config))
self.assertFalse(re.search('debug', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is -debug for Configuration={0}'.format(config))
elif cue.ci['debug']:
self.assertFalse(re.search('static', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH (found {0}) is -static for Configuration={1}'
.format(os.environ['EPICS_HOST_ARCH'], config))
self.assertTrue(re.search('-debug$', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH (found {0}) is not -debug for Configuration={1}'
.format(os.environ['EPICS_HOST_ARCH'], config))
else:
self.assertFalse(re.search('static', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is -static for Configuration={0}'.format(config))
self.assertFalse(re.search('debug', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH is -debug for Configuration={0}'.format(config))
@unittest.skipIf(ci_os != 'windows', 'HostArchPlatform test only applies to windows')
def test_HostArchPlatform(self):
if ci_service == 'appveyor':
platforms = ['x86', 'x64']
else:
platforms = ['x64']
for platform in platforms:
for cc in ['vs2019', 'gcc']:
cue.ci['platform'] = platform
cue.ci['compiler'] = cc
cue.setup_for_build(self.args)
self.assertTrue('EPICS_HOST_ARCH' in os.environ,
'EPICS_HOST_ARCH is not set for {0} / {1}'
.format(cc, cue.ci['platform']))
if platform == 'x86':
self.assertTrue(re.search('^win32-x86', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH (found {0}) is not win32-x86 for {1} / {2}'
.format(os.environ['EPICS_HOST_ARCH'], cc, platform))
else:
self.assertTrue(re.search('^windows-x64', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH (found {0}) is not windows-x64 for {1} / {2}'
.format(os.environ['EPICS_HOST_ARCH'], cc, platform))
if cc == 'gcc':
self.assertTrue(re.search('-mingw$', os.environ['EPICS_HOST_ARCH']),
'EPICS_HOST_ARCH (found {0}) is not -mingw for {1} / {2}'
.format(os.environ['EPICS_HOST_ARCH'], cc, platform))
if ci_service == 'appveyor':
pattern = {'x86': 'mingw32', 'x64': 'mingw64'}
self.assertTrue(re.search(pattern[platform], os.environ['PATH']),
'Binary location for {0} not in PATH (found PATH = {1})'
.format(pattern[platform], os.environ['PATH']))
@unittest.skipIf(ci_os != 'windows', 'Strawberry perl test only applies to windows')
def test_StrawberryInPath(self):
cue.setup_for_build(self.args)
self.assertTrue(re.search('strawberry', os.environ['PATH'], flags=re.IGNORECASE),
'Strawberry Perl location not in PATH (found PATH = {0})'
.format(os.environ['PATH']))
def setBase314(self, yesno):
cfg_base_version = os.path.join('configure', 'CONFIG_BASE_VERSION')
fout = open(cfg_base_version, 'w')
print('# test file for base version detection', file=fout)
print('BASE_3_14={0}'.format(yesno), file=fout)
fout.close()
def setTestResultsTarget(self, target):
rules_build = os.path.join('configure', 'RULES_BUILD')
fout = open(rules_build, 'w')
print('# test file for target detection', file=fout)
print('{0}: something'.format(target), file=fout)
fout.close()
def test_DetectionBase314No(self):
self.setBase314('NO')
cue.setup_for_build(self.args)
self.assertFalse(cue.is_base314, 'Falsely detected Base 3.14')
def test_DetectionBase314Yes(self):
self.setBase314('YES')
cue.setup_for_build(self.args)
self.assertTrue(cue.is_base314, 'Base 3.14 = YES not detected')
def test_DetectionTestResultsTarget314No(self):
self.setBase314('YES')
self.setTestResultsTarget('nottherighttarget')
cue.setup_for_build(self.args)
self.assertFalse(cue.has_test_results, 'Falsely detected test-results target')
def test_DetectionTestResultsTarget314Yes(self):
self.setBase314('YES')
self.setTestResultsTarget('test-results')
cue.setup_for_build(self.args)
self.assertFalse(cue.has_test_results, 'Falsely found test-results on Base 3.14')
def test_DetectionTestResultsTargetNot314Yes(self):
self.setBase314('NO')
self.setTestResultsTarget('test-results')
cue.setup_for_build(self.args)
self.assertTrue(cue.has_test_results, 'Target test-results not detected')
def test_ExtraMakeArgs(self):
os.environ['EXTRA'] = 'bla'
for ind in range(1,5):
os.environ['EXTRA{0}'.format(ind)] = 'bla {0}'.format(ind)
cue.setup_for_build(self.args)
self.assertTrue(cue.extra_makeargs[0] == 'bla', 'Extra make arg [0] not set')
for ind in range(1,5):
self.assertTrue(cue.extra_makeargs[ind] == 'bla {0}'.format(ind),
'Extra make arg [{0}] not set (expected "bla {0}", found "{1}")'
.format(ind, cue.extra_makeargs[ind]))
class TestHooks(unittest.TestCase):
location = os.path.join(cue.cachedir, 'hook_test')
bla_file = os.path.join(location, 'bla.txt')
new_file = os.path.join(location, 'dd', 'new.txt')
def setUp(self):
if os.path.exists(self.location):
shutil.rmtree(self.location, onerror=cue.remove_readonly)
try:
os.makedirs(self.location)
except:
pass
with open(self.bla_file, 'w') as f:
f.write('''LINE1=YES
LINE2=NO''')
def test_patchfile(self):
hook = os.path.join(builddir, 'test.patch')
cue.apply_patch(hook, cwd=self.location)
line1_yes = False
with open(self.bla_file) as f:
if 'LINE1=YES' in f.read():
line1_yes = True
self.assertFalse(line1_yes, "Patch didn't change line in test file 'bla.txt'")
self.assertTrue(os.path.exists(self.new_file), "patch didn't add new file")
def test_archiveZip(self):
hook = os.path.join(builddir, 'test.zip')
cue.extract_archive(hook, cwd=self.location)
self.assertTrue(os.path.exists(self.new_file), "archive extract didn't add new file")
def test_archive7z(self):
hook = os.path.join(builddir, 'test.7z')
cue.extract_archive(hook, cwd=self.location)
self.assertTrue(os.path.exists(self.new_file), "archive extract didn't add new file")
if __name__ == "__main__":
if 'VV' in os.environ and os.environ['VV'] == '1':
logging.basicConfig(level=logging.DEBUG)
cue.silent_dep_builds = False
cue.detect_context()
cue.host_info()
if sys.argv[1:] == ['env']:
# testing with_vcvars
[print(K, '=', V) for K, V in os.environ.items()]
elif ci_os == 'windows' and sys.argv[1:] == ['findvs']:
from fnmatch import fnmatch
print('Available Visual Studio versions')
for base in (r'C:\Program Files (x86)', r'C:\Program Files'):
for root, dirs, files in os.walk(base):
for fname in files:
if fnmatch(fname, 'vcvarsall.bat'):
print('Found', os.path.join(root, fname))
sys.stdout.flush()
else:
unittest.main()

1117
cue.py

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
# EPICS Base
BASE=7.0
BASE_DIRNAME=base
BASE_REPONAME=epics-base
BASE_REPOOWNER=epics-base

View File

@@ -9,9 +9,6 @@ DB += dbExample2.db
DB += dbSubExample.db
DB += user.substitutions
# Host-side expansion of substitutions file with MSI
DB += dbExample3.db
# If <anyname>.db template is not named <anyname>*.template add
# <anyname>_TEMPLATE = <templatename>

View File

@@ -1,8 +0,0 @@
# Example host-side substitutions file
file dbExample2.db {
pattern { user, no, scan }
{ "ralph", 4, "1 second" }
{ "ralph", 5, "2 second" }
{ "ralph", 6, "5 second" }
}

View File

@@ -38,9 +38,7 @@ testHarness_SRCS += epicsRunExampleTests.c
exampleTestHarness_SRCS += $(testHarness_SRCS)
exampleTestHarness_SRCS_RTEMS += rtemsTestHarness.c
ifdef BASE_7_0
PROD_SRCS_RTEMS += rtemsTestData.c
endif
PROD_vxWorks = exampleTestHarness
PROD_RTEMS = exampleTestHarness
@@ -56,7 +54,5 @@ endif
include $(TOP)/configure/RULES
ifdef BASE_7_0
rtemsTestData.c : $(TESTFILES) $(TOOLS)/epicsMakeMemFs.pl
$(PERL) $(TOOLS)/epicsMakeMemFs.pl $@ epicsRtemsFSImage $(TESTFILES)
endif

View File

@@ -1,69 +0,0 @@
# GitHub Actions Scripts for EPICS Modules
## Features
- 20 parallel runners on Linux/Windows (5 runners on MacOS)
- Ubuntu 16/18/20, MacOS 10.15, Windows Server 2016/2019
- Compile natively on Linux (gcc, clang)
- Compile natively on MacOS (clang)
- Compile natively on Windows (gcc/MinGW, Visual Studio 2017 & 2019)
- Cross-compile for Windows 32bit and 64bit using MinGW and WINE
- Cross-compile for RTEMS 4.9 and 4.10 (Base >= 3.15)
- Caching not supported yet.
## How to Use these Scripts
1. Add the ci-scripts respository as a Git Submodule
(see [README](../README.md) one level above).
2. Add settings files defining which dependencies in which versions
you want to build against
(see [README](../README.md) one level above).
3. Create a GitHub Actions configuration by copying one of the workflow
examples into the directory `.github/workflows` of your module.
```bash
$ mkdir -p .github/workflows
$ cp .ci/github-actions/ci-scripts-build.yml.example-full .github/workflows/ci-scripts-build.yml
```
4. Edit the workflow configuration to include the build jobs you want
GitHub Actions to run.
Build jobs are specified in the `jobs: <job-name>: strategy:`
declaration. The `matrix:` element specifies the axes as configuration
parameters with their lists of values,
`env:` (on the build level) controls the setting of environment variables
(which can be matrix parameters).
The `runs-on:` setting specifies the image (operating system) of the
runner.
The `name:` is what shows up in the web interface for the workflow,
builds and jobs, and the elements under `steps:` describe the actions
executed for each job of the matrix.
Please check the comments in the examples for more hints, and the
[GitHub Actions documentation](https://help.github.com/en/actions)
for a lot more options and details.
5. Push your changes and click on the `Actions` tab of your GitHub repository
page to see your build results.
## Specifics
#### Quote Environment Variable Values
Variable settings distinguish between numerical and string values.
Better quote all branch and tag names. E.g.,
```yaml
env:
BASE: "7.0"
```
to avoid ci-scripts trying to `git clone` with `--branch 7`.
## Caches
GitHub Actions provides caching of dependencies.
However, since their cache restore and create algorithm is fundamentally
different from those used by Travis and AppVeyor, this will require some
more changes in ci-scripts to work. Be patient.

View File

@@ -1,162 +0,0 @@
# .github/workflows/ci-scripts-build.yml for use with EPICS Base ci-scripts
# (see: https://github.com/epics-base/ci-scripts)
# This is YAML - indentation levels are crucial
# Set the 'name:' properties to values that work for you (MYMODULE)
name: MYMODULE ci-scripts build
# Trigger on pushes and PRs to any branch
on: [push, pull_request]
env:
SETUP_PATH: .ci-local:.ci
CMP: gcc
# For the sequencer on Linux/Windows/MacOS
APT: re2c
CHOCO: re2c
BREW: re2c
jobs:
build-linux:
name: ${{ matrix.cmp }} / ${{ matrix.configuration }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
# Set environment variables from matrix parameters
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, ubuntu-18.04, ubuntu-16.04]
cmp: [gcc, clang]
configuration: [default, static, debug, static-debug]
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Prepare and compile dependencies
run: python .ci/cue.py prepare
- name: Build main module
run: python .ci/cue.py build
- name: Run main module tests
run: python .ci/cue.py test
- name: Collect and show test results
run: python .ci/cue.py test-results
build-macos:
name: ${{ matrix.cmp }} / ${{ matrix.configuration }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
# Set environment variables from matrix parameters
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
strategy:
fail-fast: false
matrix:
os: [macos-10.15]
cmp: [clang]
# No static builds on MacOS
configuration: [default, debug]
steps:
- uses: actions/checkout@v2
- name: Prepare and compile dependencies
run: python .ci/cue.py prepare
- name: Build main module
run: python .ci/cue.py build
- name: Run main module tests
run: python .ci/cue.py test
- name: Collect and show test results
run: python .ci/cue.py test-results
build-windows:
name: ${{ matrix.cmp }} / ${{ matrix.configuration }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
strategy:
fail-fast: false
matrix:
os: [windows-2019, windows-2016]
cmp: [gcc, vs2019, vs2017]
configuration: [default, static, debug, static-debug]
# Available: vs2017/windows-2016 and vs2019/windows-2019
exclude:
- os: windows-2019
cmp: vs2017
- os: windows-2016
cmp: vs2019
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Prepare and compile dependencies
run: python .ci/cue.py prepare
- name: Build main module
run: python .ci/cue.py build
- name: Run main module tests
run: python .ci/cue.py test
- name: Collect and show test results
run: python .ci/cue.py test-results
# Same setup and toolchain as on Travis.
# Needs Base >= 3.15 to compile, EPICS 7 to also run the tests on qemu
build-rtems:
name: RTEMS${{ matrix.rtems }} / ${{ matrix.configuration }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
RTEMS: ${{ matrix.rtems }}
APT: re2c g++-mingw-w64-i686 g++-mingw-w64-x86-64 qemu-system-x86
strategy:
fail-fast: false
matrix:
os: [ubuntu-18.04]
cmp: [gcc]
configuration: [default]
rtems: ["4.9", "4.10"]
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Prepare and compile dependencies
run: python .ci/cue.py prepare
- name: Build main module
run: python .ci/cue.py build
- name: Run main module tests
run: python .ci/cue.py test
- name: Collect and show test results
run: python .ci/cue.py test-results
# The WINE cross builds are of somewhat limited use,
# as there are native gcc/MinGW builds available on GitHub Actions
build-wine:
name: WINE${{ matrix.wine }} / ${{ matrix.configuration }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
WINE: ${{ matrix.wine }}
APT: re2c g++-mingw-w64-i686 g++-mingw-w64-x86-64
strategy:
fail-fast: false
matrix:
os: [ubuntu-18.04]
cmp: [gcc]
configuration: [default, static, debug, static-debug]
wine: [32, 64]
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Prepare and compile dependencies
run: python .ci/cue.py prepare
- name: Build main module
run: python .ci/cue.py build
- name: Run main module tests
run: python .ci/cue.py test
- name: Collect and show test results
run: python .ci/cue.py test-results

View File

@@ -1,44 +0,0 @@
# .github/workflows/ci-scripts-build.yml for use with EPICS Base ci-scripts
# (see: https://github.com/epics-base/ci-scripts)
# This is YAML - indentation levels are crucial
# Set the 'name:' properties to values that work for you
name: MYMODULE ci-scripts build
# Trigger on pushes and PRs to any branch
on: [push, pull_request]
env:
SETUP_PATH: .ci-local:.ci
CMP: gcc
jobs:
build-linux:
name: ${{ matrix.base }} / ${{ matrix.cmp }} / ${{ matrix.configuration }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
# Set environment variables from matrix parameters
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
BASE: ${{ matrix.base }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, ubuntu-18.04]
cmp: [gcc]
configuration: [default, static]
base: ["7.0", "3.15"]
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Prepare and compile dependencies
run: python .ci/cue.py prepare
- name: Build main module
run: python .ci/cue.py build
- name: Run main module tests
run: python .ci/cue.py test
- name: Collect and show test results
run: python .ci/cue.py test-results

BIN
test.7z

Binary file not shown.

View File

@@ -1,14 +0,0 @@
diff -ruN a/bla.txt b/bla.txt
--- a/bla.txt 2020-06-19 18:54:43.129076711 +0200
+++ b/bla.txt 2020-06-19 18:55:05.093948316 +0200
@@ -1,3 +1,3 @@
-LINE1=YES
+LINE1=NO
LINE2=NO
diff -ruN a/dd/new.txt b/dd/new.txt
--- a/dd/new.txt 1970-01-01 01:00:00.000000000 +0100
+++ b/dd/new.txt 2020-06-19 18:55:35.255032413 +0200
@@ -0,0 +1,2 @@
+NEW LINE 1
+NEW LINE 2

BIN
test.zip

Binary file not shown.

View File

@@ -1,4 +1,4 @@
MODULES="sncseq"
BASE=7.0
SNCSEQ=R2-2-8
SNCSEQ=R2-2-7

View File

@@ -14,8 +14,6 @@ cache:
env:
global:
- SETUP_PATH=.ci-local:.ci
# for the sequencer on Windows
- CHOCO=re2c
addons:
apt:
@@ -31,23 +29,19 @@ addons:
- g++-mingw-w64-x86-64
# for RTEMS cross builds
- qemu-system-x86
# for .zip/.7z archive hooks
- p7zip-full
homebrew:
packages:
# for all EPICS builds
- bash
# for the sequencer
- re2c
# for .zip/.7z archive hooks
- p7zip
update: true
install:
- python .ci/cue.py prepare
- ./.ci/travis/prepare.sh
script:
- python .ci/cue.py build
- python .ci/cue.py test
- python .ci/cue.py test-results
- ./.ci/travis/build.sh
# If you need to do more during install and build,
# add a local directory to your module and do e.g.
@@ -58,13 +52,11 @@ script:
# Well-known variables to use
# SET source setup file
# ADD_MODULES extra modules (for a specific job)
# BCFG build configuration (static/debug/static-debug;
# default: shared-optimized)
# EXTRA content will be added to make command line
# (embedded quotes must be escaped as \\\")
# STATIC set to YES for static build (default: NO)
# TEST set to NO to skip running the tests (default: YES)
# VV set to make build scripts verbose (default: unset)
# EXTRA content will be added to make command line
# EXTRA1..5 more additional arguments for the make command
# (one argument per variable)
# Usually from setup files, but may be specified or overridden
# on a job line
@@ -83,7 +75,7 @@ jobs:
- env: BASE=7.0
compiler: clang
- env: BASE=7.0
- env: BASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++11"
- env: BASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++11"
compiler: clang
@@ -96,17 +88,19 @@ jobs:
- env: BASE=7.0 EXTRA="CMD_CXXFLAGS=-std=c++11"
dist: trusty
# Cross-compilations to Windows using gcc/MinGW and WINE
# Cross-compilations to Windows using MinGW and WINE
- env: BASE=7.0 WINE=32 TEST=NO STATIC=YES
compiler: mingw
- env: BASE=7.0 WINE=32 TEST=NO BCFG=static
- env: BASE=7.0 WINE=64 TEST=NO
- env: BASE=7.0 WINE=64 TEST=NO STATIC=NO
compiler: mingw
# Cross-compilation to RTEMS
- env: BASE=7.0 RTEMS=4.10
- env: BASE=7.0 RTEMS=4.10 TEST=NO
- env: BASE=7.0 RTEMS=4.9
- env: BASE=7.0 RTEMS=4.9 TEST=NO
# Other gcc versions (added as an extra package)
@@ -123,12 +117,3 @@ jobs:
- env: BASE=7.0
os: osx
compiler: clang
# Windows builds
- env: BASE=7.0
os: windows
compiler: vs2017
- env: BASE=7.0
os: windows

View File

@@ -15,14 +15,12 @@ addons:
- perl
install:
- python .ci/cue.py prepare
- ./.ci/travis/prepare.sh
script:
- python .ci/cue.py build
- python .ci/cue.py test
- python .ci/cue.py test-results
- ./.ci/travis/build.sh
# Build on Linux using default gcc for Base branches 7.0 and 3.15
# Build using default gcc for Base branches 7.0 and 3.15
jobs:
include:

View File

@@ -0,0 +1,77 @@
From 00ee7bf7d3618c748491c88742c011a8353abeba Mon Sep 17 00:00:00 2001
From: Andrew Johnson <anj@anl.gov>
Date: Wed, 24 Oct 2018 14:27:15 -0500
Subject: [PATCH] Add RTEMS-pc368-qemu target, use in Travis-CI builds
---
configure/os/CONFIG.Common.RTEMS-pc386-qemu | 11 +++++++++++
configure/os/CONFIG_SITE.Common.RTEMS-pc386 | 5 -----
configure/os/CONFIG_SITE.Common.RTEMS-pc386-qemu | 9 +++++++++
src/libCom/RTEMS/Makefile | 2 +-
src/tools/makeTestfile.pl | 2 +-
6 files changed, 24 insertions(+), 10 deletions(-)
create mode 100644 configure/os/CONFIG.Common.RTEMS-pc386-qemu
delete mode 100644 configure/os/CONFIG_SITE.Common.RTEMS-pc386
create mode 100644 configure/os/CONFIG_SITE.Common.RTEMS-pc386-qemu
diff --git a/configure/os/CONFIG.Common.RTEMS-pc386-qemu b/configure/os/CONFIG.Common.RTEMS-pc386-qemu
new file mode 100644
index 000000000..684f01a19
--- /dev/null
+++ b/configure/os/CONFIG.Common.RTEMS-pc386-qemu
@@ -0,0 +1,11 @@
+# CONFIG.Common.RTEMS-pc386-qemu
+#
+# Definitions for the RTEMS-pc386-qemu target
+# Site-specific overrides go in CONFIG_SITE.Common.RTEMS-pc386-qemu
+#
+#-------------------------------------------------------
+
+# Include definitions from RTEMS-pc386
+include $(CONFIG)/os/CONFIG.Common.RTEMS-pc386
+
+RTEMS_QEMU_FIXUPS = YES
diff --git a/configure/os/CONFIG_SITE.Common.RTEMS-pc386-qemu b/configure/os/CONFIG_SITE.Common.RTEMS-pc386-qemu
new file mode 100644
index 000000000..027dcf4ab
--- /dev/null
+++ b/configure/os/CONFIG_SITE.Common.RTEMS-pc386-qemu
@@ -0,0 +1,9 @@
+# CONFIG_SITE.Common.RTEMS-pc386-qemu
+#
+# Site-specific overrides for the RTEMS-pc386-qemu target
+#
+
+# If you're building this architecture you _probably_ want to
+# run the tests for it under QEMU, but if not you can turn
+# them off here by commenting out this line:
+CROSS_COMPILER_RUNTEST_ARCHS += RTEMS-pc386-qemu
diff --git a/src/libCom/RTEMS/Makefile b/src/libCom/RTEMS/Makefile
index 2f12b7bf0..22a92733c 100644
--- a/src/libCom/RTEMS/Makefile
+++ b/src/libCom/RTEMS/Makefile
@@ -24,7 +24,7 @@ rtemsCom_SRCS += epicsRtemsInitHookPre.c
rtemsCom_SRCS += epicsRtemsInitHookPost.c
rtemsCom_SRCS += epicsMemFs.c
-ifeq ($(T_A),RTEMS-pc386)
+ifeq ($(RTEMS_BSP),pc386)
rtemsCom_SRCS += ne2kpci.c
endif
diff --git a/src/tools/makeTestfile.pl b/src/tools/makeTestfile.pl
index 73f522034..fb431fe7a 100644
--- a/src/tools/makeTestfile.pl
+++ b/src/tools/makeTestfile.pl
@@ -37,7 +37,7 @@ if( $TA =~ /^win32-x86/ && $HA !~ /^win/ ) {
$exec = "wine64 $exe";
# Run pc386 test harness w/ QEMU
-} elsif( $TA =~ /^RTEMS-pc386$/ ) {
+} elsif( $TA =~ /^RTEMS-pc386-qemu$/ ) {
$exec = "qemu-system-i386 -m 64 -no-reboot -serial stdio -display none -net nic,model=ne2k_pci -net user,restrict=yes -kernel $exe";
# Explicitly fail for other RTEMS targets
--
2.21.0.windows.1

View File

@@ -2,14 +2,12 @@
## Features
- Five parallel runners on Linux/Windows (one runner on MacOS)
- Use different compilers (gcc, clang)
- Use different gcc versions
- Cross-compile for Windows 32bit and 64bit using MinGW and WINE
- Cross-compile for RTEMS 4.9 and 4.10 (Base >= 3.15)
- Compile natively on MacOS (clang)
- Compile natively on Windows (gcc/MinGW, Visual Studio 2017)
- Built dependencies are cached (for faster builds).
- Cross-compile for RTEMS 4.9 and 4.10
- Compile on MacOS
- Released versions of dependencies are cached (for faster builds)
## How to Use these Scripts
@@ -36,7 +34,7 @@
Travis to run.
Build jobs are declared in the list following the `jobs: include:`
declaration. Each element (starting with a dash) defines the
declaration. Each element (starting with `-` in column 3) defines the
settings for one build job. `env:` controls the setting of environment
variables,`dist:` specifies the Linux distribution,
`os:` the operating system.
@@ -45,24 +43,3 @@
6. Push your changes and check
[travis-ci.org](https://travis-ci.org/) for your build results.
## Caches
Travis keeps the caches separate for different jobs. As soon as the job
description (in the `.travis.yml` configuration file) or its environment
settings change (adding a space character is enough), the cache is different
and will be rebuilt when the job runs.
This also means that changing a value inside a setup file will _not_
invalidate the cache - in that case you will have to manually delete the cache
through the Travis web interface. (Or add a space character in the job
configuration.)
Caches are automatically removed after approx. four weeks.
Your jobs will have to rebuild them once in a while.
## Miscellanea
To use the feature to extract `.zip`/`.7z` archives by setting
`*_HOOK` variables, the Linux and MacOS runners need the APT package
`p7zip-full` resp. the Homebrew package `p7zip` installed.

27
travis/build.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/bin/bash
set -e
# Set VV in .travis.yml to make scripts verbose
[ "$VV" ] && set -x
CACHEDIR=${CACHEDIR:-${HOME}/.cache}
eval $(grep "EPICS_BASE=" ${CACHEDIR}/RELEASE.local)
export EPICS_BASE
[ -z "$EPICS_HOST_ARCH" -a -f $EPICS_BASE/src/tools/EpicsHostArch.pl ] && EPICS_HOST_ARCH=$(perl $EPICS_BASE/src/tools/EpicsHostArch.pl)
[ -z "$EPICS_HOST_ARCH" -a -f $EPICS_BASE/startup/EpicsHostArch.pl ] && EPICS_HOST_ARCH=$(perl $EPICS_BASE/startup/EpicsHostArch.pl)
export EPICS_HOST_ARCH
[ -z "$EXTRA" ] && make -j2 || make -j2 "$EXTRA"
ret=0
if [ "$TEST" != "NO" ]
then
make tapfiles || ret=$?
make -sk test-results
fi
exit $ret

244
travis/prepare.sh Executable file
View File

@@ -0,0 +1,244 @@
#!/bin/bash
set -e
# The following if clause can be removed for ci-scripts major version 3
if [ "$TRAVIS_OS_NAME" == osx -a "$BASH_VERSINFO" -lt 4 ]
then
brew install bash
if [ $(/usr/local/bin/bash -c 'echo $BASH_VERSINFO') -lt 4 ]
then
echo "Failed to install a recent bash" >&2
exit 1
fi
exec /usr/local/bin/bash $0 "$@"
fi
# Set VV in .travis.yml to make scripts verbose
[ "$VV" ] && set -x
# Perl version of "readlink -f" (which MacOS does not provide)
readlinkf() { perl -MCwd -e 'print Cwd::abs_path shift' "$1"; }
SCRIPTDIR=$(dirname $(readlinkf $0))
CURDIR="$PWD"
CACHEDIR=${CACHEDIR:-${HOME}/.cache}
[ -e ${CACHEDIR} ] || mkdir -p ${CACHEDIR}
# source functions
. $SCRIPTDIR/utils.sh
echo -e "${ANSI_YELLOW}Using bash version $BASH_VERSION${ANSI_RESET}"
if [ -f /etc/hosts ]
then
# The travis-ci "bionic" image throws us a curveball in /etc/hosts
# by including two entries for localhost. The first for 127.0.1.1
# which causes epicsSockResolveTest to fail.
# cat /etc/hosts
# ...
# 127.0.1.1 localhost localhost ip4-loopback
# 127.0.0.1 localhost nettuno travis vagrant travis-job-....
sudo sed -i -e '/^127\.0\.1\.1/ s|localhost\s*||g' /etc/hosts
echo "==== /etc/hosts"
cat /etc/hosts
echo "===="
fi
# Load settings
# -------------
fold_start load.settings "Loading settings"
# load default settings for well-known modules
source_set defaults
# source configured settings
[ -z "${SET+x}" ] || source_set $SET
fold_end load.settings
# Check out dependencies
# ----------------------
if [ "$BASE" != "SELF" ]
then
fold_start check.out.dependencies "Checking/cloning dependencies"
for mod in BASE $ADD_MODULES $MODULES
do
mod_uc=${mod^^}
eval add_dependency $mod_uc \${${mod_uc}:=master}
done
[ -e ./configure ] && cp ${CACHEDIR}/RELEASE.local ./configure/RELEASE.local
fold_end check.out.dependencies
fi
# Set up compiler
# ---------------
fold_start set.up.epics_build "Setting up EPICS build system"
if [ "$BASE" = "SELF" ]
then
EPICS_BASE=$CURDIR
else
eval $(grep "EPICS_BASE=" ${CACHEDIR}/RELEASE.local)
fi
export EPICS_BASE
echo "EPICS_BASE=$EPICS_BASE"
[ -z "$EPICS_HOST_ARCH" -a -f $EPICS_BASE/src/tools/EpicsHostArch.pl ] && EPICS_HOST_ARCH=$(perl $EPICS_BASE/src/tools/EpicsHostArch.pl)
[ -z "$EPICS_HOST_ARCH" -a -f $EPICS_BASE/startup/EpicsHostArch.pl ] && EPICS_HOST_ARCH=$(perl $EPICS_BASE/startup/EpicsHostArch.pl)
export EPICS_HOST_ARCH
echo "EPICS_HOST_ARCH=$EPICS_HOST_ARCH"
if echo ${modules_to_compile} | grep -q "$EPICS_BASE" || [ "$BASE" = "SELF" ]
then
# requires wine and g++-mingw-w64-i686
if [ "$WINE" = "32" ]
then
echo "Cross mingw32"
sed -i -e '/CMPLR_PREFIX/d' $EPICS_BASE/configure/os/CONFIG_SITE.linux-x86.win32-x86-mingw
cat << EOF >> $EPICS_BASE/configure/os/CONFIG_SITE.linux-x86.win32-x86-mingw
CMPLR_PREFIX=i686-w64-mingw32-
EOF
cat << EOF >> $EPICS_BASE/configure/CONFIG_SITE
CROSS_COMPILER_TARGET_ARCHS+=win32-x86-mingw
EOF
elif [ "$WINE" = "64" ]
then
echo "Cross mingw64"
sed -i -e '/CMPLR_PREFIX/d' $EPICS_BASE/configure/os/CONFIG_SITE.linux-x86.windows-x64-mingw
cat << EOF >> $EPICS_BASE/configure/os/CONFIG_SITE.linux-x86.windows-x64-mingw
CMPLR_PREFIX=x86_64-w64-mingw32-
EOF
cat << EOF >> $EPICS_BASE/configure/CONFIG_SITE
CROSS_COMPILER_TARGET_ARCHS+=windows-x64-mingw
EOF
fi
if [ "$STATIC" = "YES" ]
then
echo "Build static libraries/executables"
cat << EOF >> $EPICS_BASE/configure/CONFIG_SITE
SHARED_LIBRARIES=NO
STATIC_BUILD=YES
EOF
fi
HOST_CCMPLR_NAME=`echo "$TRAVIS_COMPILER" | sed -E 's/^([[:alpha:]][^-]*(-[[:alpha:]][^-]*)*)+(-[0-9\.]+)?$/\1/g'`
HOST_CMPLR_VER_SUFFIX=`echo "$TRAVIS_COMPILER" | sed -E 's/^([[:alpha:]][^-]*(-[[:alpha:]][^-]*)*)+(-[0-9\.]+)?$/\3/g'`
HOST_CMPLR_VER=`echo "$HOST_CMPLR_VER_SUFFIX" | cut -c 2-`
case "$HOST_CCMPLR_NAME" in
clang)
echo "Host compiler is clang"
HOST_CPPCMPLR_NAME=$(echo "$HOST_CCMPLR_NAME" | sed 's/clang/clang++/g')
cat << EOF >> $EPICS_BASE/configure/os/CONFIG_SITE.Common.$EPICS_HOST_ARCH
GNU = NO
CMPLR_CLASS = clang
CC = ${HOST_CCMPLR_NAME}$HOST_CMPLR_VER_SUFFIX
CCC = ${HOST_CPPCMPLR_NAME}$HOST_CMPLR_VER_SUFFIX
EOF
# hack
sed -i -e 's/CMPLR_CLASS = gcc/CMPLR_CLASS = clang/' $EPICS_BASE/configure/CONFIG.gnuCommon
${HOST_CCMPLR_NAME}$HOST_CMPLR_VER_SUFFIX --version
;;
gcc)
echo "Host compiler is GCC"
HOST_CPPCMPLR_NAME=$(echo "$HOST_CCMPLR_NAME" | sed 's/gcc/g++/g')
cat << EOF >> $EPICS_BASE/configure/os/CONFIG_SITE.Common.$EPICS_HOST_ARCH
CC = ${HOST_CCMPLR_NAME}$HOST_CMPLR_VER_SUFFIX
CCC = ${HOST_CPPCMPLR_NAME}$HOST_CMPLR_VER_SUFFIX
EOF
${HOST_CCMPLR_NAME}$HOST_CMPLR_VER_SUFFIX --version
;;
*)
echo "Host compiler is default"
gcc --version
;;
esac
cat <<EOF >> $EPICS_BASE/configure/CONFIG_SITE
USR_CPPFLAGS += $USR_CPPFLAGS
USR_CFLAGS += $USR_CFLAGS
USR_CXXFLAGS += $USR_CXXFLAGS
EOF
# set RTEMS to eg. "4.9" or "4.10"
# requires qemu, bison, flex, texinfo, install-info
if [ -n "$RTEMS" ]
then
echo "Cross RTEMS${RTEMS} for pc386"
sed -i -e '/^RTEMS_VERSION/d' -e '/^RTEMS_BASE/d' $EPICS_BASE/configure/os/CONFIG_SITE.Common.RTEMS
cat << EOF >> $EPICS_BASE/configure/os/CONFIG_SITE.Common.RTEMS
RTEMS_VERSION=$RTEMS
RTEMS_BASE=$HOME/.rtems
EOF
cat << EOF >> $EPICS_BASE/configure/CONFIG_SITE
CROSS_COMPILER_TARGET_ARCHS += RTEMS-pc386-qemu
EOF
fi
else
echo -e "${ANSI_GREEN}EPICS build system already set up (Base was loaded from cache)${ANSI_RESET}"
fi
# Download RTEMS cross compiler
if [ -n "$RTEMS" ]
then
echo "Downloading RTEMS${RTEMS} cross compiler for pc386"
curl -L "https://github.com/mdavidsaver/rsb/releases/download/20171203-${RTEMS}/i386-rtems${RTEMS}-trusty-20171203-${RTEMS}.tar.bz2" \
| tar -C / -xmj
fi
fold_end set.up.compiler
echo "\$ make --version"
make --version
[ "$BASE" = "SELF" ] && exit 0
# Build required dependencies
# ---------------------------
fold_start build.dependencies "Build missing/outdated dependencies"
[ "$VV" ] && silent="-s" || silent=
[ -z "$modules_to_compile" ] && echo -e "${ANSI_GREEN}All dependency modules are up-to-date (nothing to do)${ANSI_RESET}"
for module in ${modules_to_compile}
do
eval name=\${module#${CACHEDIR}/}
fold_start build.$name "Build $name"
make -j2 $silent -C $module $EXTRA
fold_end build.$name
done
fold_end build.dependencies
echo -e "${ANSI_BLUE}Dependency module information${ANSI_RESET}"
echo "Module Tag Binaries Commit"
echo "-----------------------------------------------------------------------------------"
for mod in base $MODULES $ADD_MODULES
do
mod_uc=${mod^^}
eval tag=\${${mod_uc}}
eval dir=${CACHEDIR}/\${${mod_uc}_DIRNAME}-$tag
echo "$modules_to_compile" | grep -q "$dir" && stat="rebuilt" || stat="from cache"
commit=$(git -C $dir log -n1 --oneline)
printf "%-10s %-12s %-11s %s\n" "$mod" "$tag" "$stat" "$commit"
done
echo -e "${ANSI_BLUE}Contents of RELEASE.local${ANSI_RESET}"
cat ${CACHEDIR}/RELEASE.local

218
travis/utils.sh Normal file
View File

@@ -0,0 +1,218 @@
# Utility functions for Travis scripts in ci-scripts
#
# This file is sourced by the executable scripts
# CACHEDIR must be defined and existing before calling these functions
# Portable version of 'sed -i' (that MacOS doesn't provide)
# sedi (cmd, file)
# Do the equivalent of "sed -i cmd file"
sedi () {
cat $2 | sed "$1" > $2.tmp$$; mv -f $2.tmp$$ $2
}
# Setup ANSI Colors
export ANSI_RED="\033[31;1m"
export ANSI_GREEN="\033[32;1m"
export ANSI_YELLOW="\033[33;1m"
export ANSI_BLUE="\033[34;1m"
export ANSI_RESET="\033[0m"
export ANSI_CLEAR="\033[0K"
# Travis log fold control
# from https://github.com/travis-ci/travis-rubies/blob/build/build.sh
fold_start() {
echo -e "travis_fold:start:$1\\r${ANSI_YELLOW}$2${ANSI_RESET}"
}
fold_end() {
echo -en "travis_fold:end:$1\\r"
}
die() {
echo -e "${ANSI_RED}$1${ANSI_RESET}"
[ "$UTILS_UNITTEST" ] || exit 1
}
# source_set(settings)
#
# Source a settings file (extension .set) found in SETUP_PATH
# May be called recursively (from within a settings file)
declare -a SEEN_SETUPS
source_set() {
local set_file=${1//[$'\r']}
local set_dir
local found=0
[ "${SETUP_PATH}" ] || die "Search path for setup files (SETUP_PATH) is empty"
for set_dir in ${SETUP_PATH//:/ }
do
if [ -e $set_dir/$set_file.set ]
then
if [[ " ${SEEN_SETUPS[@]} " =~ " $set_dir/$set_file.set " ]]
then
echo "Ignoring already included setup file $set_dir/$set_file.set"
return
fi
SEEN_SETUPS+=($set_dir/$set_file.set)
echo "Loading setup file $set_dir/$set_file.set"
local line
while read -r line
do
[ -z "$line" ] && continue
echo $line | grep -q "^#" && continue
if echo $line | grep -q "^include\W"
then
source_set $(echo $line | awk '{ print $2 }')
continue
fi
if echo "$line" | grep -q "^\w\+="
then
IFS== read var value <<< "${line//[$'\r']}"
value=$(sed "s/^\(\"\)\(.*\)\1\$/\2/g" <<< "$value") # remove surrounding quotes
eval [ "\${$var}" ] || eval "$var=\$value"
fi
done < $set_dir/$set_file.set
found=1
break
fi
done
[ $found -ne 0 ] || die "Setup file $set_file.set does not exist in SETUP_PATH search path ($SETUP_PATH)"
}
# update_release_local(varname, place)
# varname name of the variable to set in RELEASE.local
# place place (absolute path) of where variable should point to
#
# Manipulate RELEASE.local in the cache location:
# - replace "$varname=$place" line if it exists and has changed
# - otherwise add "$varname=$place" line and possibly move EPICS_BASE=... line to the end
update_release_local() {
local var=$1
local place=$2
local release_local=${CACHEDIR}/RELEASE.local
local updated_line="${var}=${place}"
local ret=0
[ -e ${release_local} ] && grep -q "${var}=" ${release_local} || ret=$?
if [ $ret -eq 0 ]
then
existing_line=$(grep "${var}=" ${release_local})
if [ "${existing_line}" != "${updated_line}" ]
then
sedi "s|${var}=.*|${var}=${place}|g" ${release_local}
fi
else
echo "$var=$place" >> ${release_local}
ret=0
grep -q "EPICS_BASE=" ${release_local} || ret=$?
if [ $ret -eq 0 ]
then
base_line=$(grep "EPICS_BASE=" ${release_local})
sedi '\|EPICS_BASE=|d' ${release_local}
echo ${base_line} >> ${release_local}
fi
fi
}
# add_dependency(dep, tag)
#
# Add a dependency to the cache area:
# - check out (recursive if configured) in the CACHE area unless it already exists and the
# required commit has been built
# - Defaults:
# $dep_DIRNAME = lower case ($dep)
# $dep_REPONAME = lower case ($dep)
# $dep_REPOURL = GitHub / $dep_REPOOWNER (or $REPOOWNER or epics-modules) / $dep_REPONAME .git
# $dep_VARNAME = $dep
# $dep_DEPTH = 5
# $dep_RECURSIVE = 1/YES (0/NO to for a flat clone)
# - Add $dep_VARNAME line to the RELEASE.local file in the cache area (unless already there)
# - Add full path to $modules_to_compile
add_dependency() {
curdir="$PWD"
DEP=$1
TAG=$2
dep_lc=${DEP,,}
eval dirname=\${${DEP}_DIRNAME:=${dep_lc}}
eval reponame=\${${DEP}_REPONAME:=${dep_lc}}
eval repourl=\${${DEP}_REPOURL:="https://github.com/\${${DEP}_REPOOWNER:=${REPOOWNER:-epics-modules}}/${reponame}.git"}
eval varname=\${${DEP}_VARNAME:=${DEP}}
eval recursive=\${${DEP}_RECURSIVE:=1}
recursive=${recursive,,}
local recurse=""
[ "$recursive" != "0" -a "$recursive" != "no" ] && recurse="--recursive"
# determine if $DEP points to a valid release or branch
git ls-remote --quiet --exit-code --refs $repourl "$TAG" > /dev/null 2>&1 ||
die "$TAG is neither a tag nor a branch name for $DEP ($repourl)"
if [ -e $CACHEDIR/$dirname-$TAG ]
then
[ -e $CACHEDIR/$dirname-$TAG/built ] && BUILT=$(cat $CACHEDIR/$dirname-$TAG/built) || BUILT="never"
HEAD=$(cd "$CACHEDIR/$dirname-$TAG" && git log -n1 --pretty=format:%H)
if [ "$HEAD" != "$BUILT" ]
then
rm -fr $CACHEDIR/$dirname-$TAG
else
echo "Found $TAG of dependency $DEP in $CACHEDIR/$dirname-$TAG"
fi
fi
if [ ! -e $CACHEDIR/$dirname-$TAG ]
then
cd $CACHEDIR
eval depth=\${${DEP}_DEPTH:-"-1"}
case ${depth} in
-1 )
deptharg="--depth 5"
;;
0 )
deptharg=""
;;
* )
deptharg="--depth $depth"
;;
esac
echo "Cloning $TAG of dependency $DEP into $CACHEDIR/$dirname-$TAG"
git clone --quiet $deptharg $recurse --branch "$TAG" $repourl $dirname-$TAG
( cd $dirname-$TAG && git log -n1 )
do_recompile=yes
# add MSI to Base 3.14
if [ $DEP == "BASE" ]
then
versionfile=$CACHEDIR/$dirname-$TAG/configure/CONFIG_BASE_VERSION
if [ -e ${versionfile} ] && grep -q "BASE_3_14=YES" ${versionfile}
then
echo "Adding MSI 1.7 to $CACHEDIR/$dirname-$TAG"
( cd $dirname-$TAG; patch -p0 < $SCRIPTDIR/../add-msi-to-314.patch )
fi
else
# fix non-base modules that do not include the .local files in configure/RELEASE
release=$CACHEDIR/$dirname-$TAG/configure/RELEASE
if [ -e $release ]
then
echo "-include \$(TOP)/../RELEASE.local" > $release
fi
fi
# run hook
eval hook="\${${DEP}_HOOK}"
if [ "$hook" ]
then
if [ -x "$curdir/$hook" ]
then
echo "Running hook $hook in $CACHEDIR/$dirname-$TAG"
( cd $CACHEDIR/$dirname-$TAG; "$curdir/$hook" )
else
die "Hook script $hook is not executable or does not exist."
fi
fi
HEAD=$(cd "$CACHEDIR/$dirname-$TAG" && git log -n1 --pretty=format:%H)
echo "$HEAD" > "$CACHEDIR/$dirname-$TAG/built"
cd "$curdir"
fi
[ "${do_recompile}" ] && modules_to_compile="${modules_to_compile} $CACHEDIR/$dirname-$TAG"
update_release_local ${varname} $CACHEDIR/$dirname-$TAG
}