sending to radxa
commit
c2d0bf956b
|
|
@ -0,0 +1,247 @@
|
|||
<#
|
||||
.Synopsis
|
||||
Activate a Python virtual environment for the current PowerShell session.
|
||||
|
||||
.Description
|
||||
Pushes the python executable for a virtual environment to the front of the
|
||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||
in a Python virtual environment. Makes use of the command line switches as
|
||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||
|
||||
.Parameter VenvDir
|
||||
Path to the directory that contains the virtual environment to activate. The
|
||||
default value for this is the parent of the directory that the Activate.ps1
|
||||
script is located within.
|
||||
|
||||
.Parameter Prompt
|
||||
The prompt prefix to display when this virtual environment is activated. By
|
||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||
|
||||
.Example
|
||||
Activate.ps1
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Verbose
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and shows extra information about the activation as it executes.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||
Activates the Python virtual environment located in the specified location.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Prompt "MyPython"
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and prefixes the current prompt with the specified string (surrounded in
|
||||
parentheses) while the virtual environment is active.
|
||||
|
||||
.Notes
|
||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||
execution policy for the user. You can do this by issuing the following PowerShell
|
||||
command:
|
||||
|
||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
For more information on Execution Policies:
|
||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$VenvDir,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$Prompt
|
||||
)
|
||||
|
||||
<# Function declarations --------------------------------------------------- #>
|
||||
|
||||
<#
|
||||
.Synopsis
|
||||
Remove all shell session elements added by the Activate script, including the
|
||||
addition of the virtual environment's Python executable from the beginning of
|
||||
the PATH variable.
|
||||
|
||||
.Parameter NonDestructive
|
||||
If present, do not remove this function from the global namespace for the
|
||||
session.
|
||||
|
||||
#>
|
||||
function global:deactivate ([switch]$NonDestructive) {
|
||||
# Revert to original values
|
||||
|
||||
# The prior prompt:
|
||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
|
||||
# The prior PYTHONHOME:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
}
|
||||
|
||||
# The prior PATH:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
||||
}
|
||||
|
||||
# Just remove the VIRTUAL_ENV altogether:
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV
|
||||
}
|
||||
|
||||
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
||||
}
|
||||
|
||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||
}
|
||||
|
||||
# Leave deactivate function in the global namespace if requested:
|
||||
if (-not $NonDestructive) {
|
||||
Remove-Item -Path function:deactivate
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.Description
|
||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
||||
given folder, and returns them in a map.
|
||||
|
||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
||||
then it is considered a `key = value` line. The left hand string is the key,
|
||||
the right hand is the value.
|
||||
|
||||
If the value starts with a `'` or a `"` then the first and last character is
|
||||
stripped from the value before being captured.
|
||||
|
||||
.Parameter ConfigDir
|
||||
Path to the directory that contains the `pyvenv.cfg` file.
|
||||
#>
|
||||
function Get-PyVenvConfig(
|
||||
[String]
|
||||
$ConfigDir
|
||||
) {
|
||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||
|
||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||
|
||||
# An empty map will be returned if no config file is found.
|
||||
$pyvenvConfig = @{ }
|
||||
|
||||
if ($pyvenvConfigPath) {
|
||||
|
||||
Write-Verbose "File exists, parse `key = value` lines"
|
||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||
|
||||
$pyvenvConfigContent | ForEach-Object {
|
||||
$keyval = $PSItem -split "\s*=\s*", 2
|
||||
if ($keyval[0] -and $keyval[1]) {
|
||||
$val = $keyval[1]
|
||||
|
||||
# Remove extraneous quotations around a string value.
|
||||
if ("'""".Contains($val.Substring(0, 1))) {
|
||||
$val = $val.Substring(1, $val.Length - 2)
|
||||
}
|
||||
|
||||
$pyvenvConfig[$keyval[0]] = $val
|
||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pyvenvConfig
|
||||
}
|
||||
|
||||
|
||||
<# Begin Activate script --------------------------------------------------- #>
|
||||
|
||||
# Determine the containing directory of this script
|
||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||
|
||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||
|
||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
||||
# First, get the location of the virtual environment, it might not be
|
||||
# VenvExecDir if specified on the command line.
|
||||
if ($VenvDir) {
|
||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||
Write-Verbose "VenvDir=$VenvDir"
|
||||
}
|
||||
|
||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
||||
# as `prompt`.
|
||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||
|
||||
# Next, set the prompt from the command line, or the config file, or
|
||||
# just use the name of the virtual environment folder.
|
||||
if ($Prompt) {
|
||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||
$Prompt = $pyvenvCfg['prompt'];
|
||||
}
|
||||
else {
|
||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose "Prompt = '$Prompt'"
|
||||
Write-Verbose "VenvDir='$VenvDir'"
|
||||
|
||||
# Deactivate any currently active virtual environment, but leave the
|
||||
# deactivate function in place.
|
||||
deactivate -nondestructive
|
||||
|
||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
||||
# that there is an activated venv.
|
||||
$env:VIRTUAL_ENV = $VenvDir
|
||||
|
||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||
|
||||
Write-Verbose "Setting prompt to '$Prompt'"
|
||||
|
||||
# Set the prompt to include the env name
|
||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||
|
||||
function global:prompt {
|
||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
||||
_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
||||
}
|
||||
|
||||
# Clear PYTHONHOME
|
||||
if (Test-Path -Path Env:PYTHONHOME) {
|
||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
Remove-Item -Path Env:PYTHONHOME
|
||||
}
|
||||
|
||||
# Add the venv to the PATH
|
||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
# This file must be used with "source bin/activate" *from bash*
|
||||
# You cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# Call hash to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
hash -r 2> /dev/null
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
unset VIRTUAL_ENV_PROMPT
|
||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
# on Windows, a path can contain colons and backslashes and has to be converted:
|
||||
if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
|
||||
# transform D:\path\to\venv to /d/path/to/venv on MSYS
|
||||
# and to /cygdrive/d/path/to/venv on Cygwin
|
||||
export VIRTUAL_ENV=$(cygpath /home/jake/ros2_ws/.venv)
|
||||
else
|
||||
# use the path as-is
|
||||
export VIRTUAL_ENV=/home/jake/ros2_ws/.venv
|
||||
fi
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/"bin":$PATH"
|
||||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
PS1='(.venv) '"${PS1:-}"
|
||||
export PS1
|
||||
VIRTUAL_ENV_PROMPT='(.venv) '
|
||||
export VIRTUAL_ENV_PROMPT
|
||||
fi
|
||||
|
||||
# Call hash to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
hash -r 2> /dev/null
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV /home/jake/ros2_ws/.venv
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
set prompt = '(.venv) '"$prompt"
|
||||
setenv VIRTUAL_ENV_PROMPT '(.venv) '
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
||||
# (https://fishshell.com/). You cannot run it directly.
|
||||
|
||||
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
||||
# reset old environment variables
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
# prevents error when using nested fish instances (Issue #93858)
|
||||
if functions -q _old_fish_prompt
|
||||
functions -e fish_prompt
|
||||
functions -c _old_fish_prompt fish_prompt
|
||||
functions -e _old_fish_prompt
|
||||
end
|
||||
end
|
||||
|
||||
set -e VIRTUAL_ENV
|
||||
set -e VIRTUAL_ENV_PROMPT
|
||||
if test "$argv[1]" != "nondestructive"
|
||||
# Self-destruct!
|
||||
functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV /home/jake/ros2_ws/.venv
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
|
||||
|
||||
# Unset PYTHONHOME if set.
|
||||
if set -q PYTHONHOME
|
||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||
set -e PYTHONHOME
|
||||
end
|
||||
|
||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
# fish uses a function instead of an env var to generate the prompt.
|
||||
|
||||
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||
functions -c fish_prompt _old_fish_prompt
|
||||
|
||||
# With the original prompt function renamed, we can override with our own.
|
||||
function fish_prompt
|
||||
# Save the return status of the last command.
|
||||
set -l old_status $status
|
||||
|
||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||
printf "%s%s%s" (set_color 4B8BBE) '(.venv) ' (set_color normal)
|
||||
|
||||
# Restore the return status of the previous command.
|
||||
echo "exit $old_status" | .
|
||||
# Output the original/"old" prompt.
|
||||
_old_fish_prompt
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
set -gx VIRTUAL_ENV_PROMPT '(.venv) '
|
||||
end
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from catkin_pkg.cli.create_pkg import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from catkin_pkg.cli.find_pkg import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from catkin_pkg.cli.generate_changelog import main_catching_runtime_error
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main_catching_runtime_error())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from catkin_pkg.cli.package_version import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from catkin_pkg.cli.prepare_release import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from catkin_pkg.cli.tag_changelog import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from catkin_pkg.cli.test_changelog import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from docutils.__main__ import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from numpy.f2py.f2py2e import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from numpy._configtool import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pybind11.__main__ import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1 @@
|
|||
python3
|
||||
|
|
@ -0,0 +1 @@
|
|||
/usr/bin/python3
|
||||
|
|
@ -0,0 +1 @@
|
|||
python3
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from docutils.core import rst2html
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(rst2html())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from docutils.core import rst2html4
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(rst2html4())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from docutils.core import rst2html5
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(rst2html5())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from docutils.core import rst2latex
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(rst2latex())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from docutils.core import rst2man
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(rst2man())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from docutils.core import rst2odt
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(rst2odt())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from docutils.core import rst2pseudoxml
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(rst2pseudoxml())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from docutils.core import rst2s5
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(rst2s5())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from docutils.core import rst2xetex
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(rst2xetex())
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/home/jake/ros2_ws/.venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from docutils.core import rst2xml
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(rst2xml())
|
||||
Binary file not shown.
|
|
@ -0,0 +1,239 @@
|
|||
# don't import any costly modules
|
||||
import os
|
||||
import sys
|
||||
|
||||
report_url = (
|
||||
"https://github.com/pypa/setuptools/issues/new?template=distutils-deprecation.yml"
|
||||
)
|
||||
|
||||
|
||||
def warn_distutils_present():
|
||||
if 'distutils' not in sys.modules:
|
||||
return
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"Distutils was imported before Setuptools, but importing Setuptools "
|
||||
"also replaces the `distutils` module in `sys.modules`. This may lead "
|
||||
"to undesirable behaviors or errors. To avoid these issues, avoid "
|
||||
"using distutils directly, ensure that setuptools is installed in the "
|
||||
"traditional way (e.g. not an editable install), and/or make sure "
|
||||
"that setuptools is always imported before distutils."
|
||||
)
|
||||
|
||||
|
||||
def clear_distutils():
|
||||
if 'distutils' not in sys.modules:
|
||||
return
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"Setuptools is replacing distutils. Support for replacing "
|
||||
"an already imported distutils is deprecated. In the future, "
|
||||
"this condition will fail. "
|
||||
f"Register concerns at {report_url}"
|
||||
)
|
||||
mods = [
|
||||
name
|
||||
for name in sys.modules
|
||||
if name == "distutils" or name.startswith("distutils.")
|
||||
]
|
||||
for name in mods:
|
||||
del sys.modules[name]
|
||||
|
||||
|
||||
def enabled():
|
||||
"""
|
||||
Allow selection of distutils by environment variable.
|
||||
"""
|
||||
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
|
||||
if which == 'stdlib':
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"Reliance on distutils from stdlib is deprecated. Users "
|
||||
"must rely on setuptools to provide the distutils module. "
|
||||
"Avoid importing distutils or import setuptools first, "
|
||||
"and avoid setting SETUPTOOLS_USE_DISTUTILS=stdlib. "
|
||||
f"Register concerns at {report_url}"
|
||||
)
|
||||
return which == 'local'
|
||||
|
||||
|
||||
def ensure_local_distutils():
|
||||
import importlib
|
||||
|
||||
clear_distutils()
|
||||
|
||||
# With the DistutilsMetaFinder in place,
|
||||
# perform an import to cause distutils to be
|
||||
# loaded from setuptools._distutils. Ref #2906.
|
||||
with shim():
|
||||
importlib.import_module('distutils')
|
||||
|
||||
# check that submodules load as expected
|
||||
core = importlib.import_module('distutils.core')
|
||||
assert '_distutils' in core.__file__, core.__file__
|
||||
assert 'setuptools._distutils.log' not in sys.modules
|
||||
|
||||
|
||||
def do_override():
|
||||
"""
|
||||
Ensure that the local copy of distutils is preferred over stdlib.
|
||||
|
||||
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
|
||||
for more motivation.
|
||||
"""
|
||||
if enabled():
|
||||
warn_distutils_present()
|
||||
ensure_local_distutils()
|
||||
|
||||
|
||||
class _TrivialRe:
|
||||
def __init__(self, *patterns) -> None:
|
||||
self._patterns = patterns
|
||||
|
||||
def match(self, string):
|
||||
return all(pat in string for pat in self._patterns)
|
||||
|
||||
|
||||
class DistutilsMetaFinder:
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
# optimization: only consider top level modules and those
|
||||
# found in the CPython test suite.
|
||||
if path is not None and not fullname.startswith('test.'):
|
||||
return None
|
||||
|
||||
method_name = 'spec_for_{fullname}'.format(**locals())
|
||||
method = getattr(self, method_name, lambda: None)
|
||||
return method()
|
||||
|
||||
def spec_for_distutils(self):
|
||||
if self.is_cpython():
|
||||
return None
|
||||
|
||||
import importlib
|
||||
import importlib.abc
|
||||
import importlib.util
|
||||
|
||||
try:
|
||||
mod = importlib.import_module('setuptools._distutils')
|
||||
except Exception:
|
||||
# There are a couple of cases where setuptools._distutils
|
||||
# may not be present:
|
||||
# - An older Setuptools without a local distutils is
|
||||
# taking precedence. Ref #2957.
|
||||
# - Path manipulation during sitecustomize removes
|
||||
# setuptools from the path but only after the hook
|
||||
# has been loaded. Ref #2980.
|
||||
# In either case, fall back to stdlib behavior.
|
||||
return None
|
||||
|
||||
class DistutilsLoader(importlib.abc.Loader):
|
||||
def create_module(self, spec):
|
||||
mod.__name__ = 'distutils'
|
||||
return mod
|
||||
|
||||
def exec_module(self, module):
|
||||
pass
|
||||
|
||||
return importlib.util.spec_from_loader(
|
||||
'distutils', DistutilsLoader(), origin=mod.__file__
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def is_cpython():
|
||||
"""
|
||||
Suppress supplying distutils for CPython (build and tests).
|
||||
Ref #2965 and #3007.
|
||||
"""
|
||||
return os.path.isfile('pybuilddir.txt')
|
||||
|
||||
def spec_for_pip(self):
|
||||
"""
|
||||
Ensure stdlib distutils when running under pip.
|
||||
See pypa/pip#8761 for rationale.
|
||||
"""
|
||||
if sys.version_info >= (3, 12) or self.pip_imported_during_build():
|
||||
return
|
||||
clear_distutils()
|
||||
self.spec_for_distutils = lambda: None
|
||||
|
||||
@classmethod
|
||||
def pip_imported_during_build(cls):
|
||||
"""
|
||||
Detect if pip is being imported in a build script. Ref #2355.
|
||||
"""
|
||||
import traceback
|
||||
|
||||
return any(
|
||||
cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def frame_file_is_setup(frame):
|
||||
"""
|
||||
Return True if the indicated frame suggests a setup.py file.
|
||||
"""
|
||||
# some frames may not have __file__ (#2940)
|
||||
return frame.f_globals.get('__file__', '').endswith('setup.py')
|
||||
|
||||
def spec_for_sensitive_tests(self):
|
||||
"""
|
||||
Ensure stdlib distutils when running select tests under CPython.
|
||||
|
||||
python/cpython#91169
|
||||
"""
|
||||
clear_distutils()
|
||||
self.spec_for_distutils = lambda: None
|
||||
|
||||
sensitive_tests = (
|
||||
[
|
||||
'test.test_distutils',
|
||||
'test.test_peg_generator',
|
||||
'test.test_importlib',
|
||||
]
|
||||
if sys.version_info < (3, 10)
|
||||
else [
|
||||
'test.test_distutils',
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
for name in DistutilsMetaFinder.sensitive_tests:
|
||||
setattr(
|
||||
DistutilsMetaFinder,
|
||||
f'spec_for_{name}',
|
||||
DistutilsMetaFinder.spec_for_sensitive_tests,
|
||||
)
|
||||
|
||||
|
||||
DISTUTILS_FINDER = DistutilsMetaFinder()
|
||||
|
||||
|
||||
def add_shim():
|
||||
DISTUTILS_FINDER in sys.meta_path or insert_shim()
|
||||
|
||||
|
||||
class shim:
|
||||
def __enter__(self) -> None:
|
||||
insert_shim()
|
||||
|
||||
def __exit__(self, exc: object, value: object, tb: object) -> None:
|
||||
_remove_shim()
|
||||
|
||||
|
||||
def insert_shim():
|
||||
sys.meta_path.insert(0, DISTUTILS_FINDER)
|
||||
|
||||
|
||||
def _remove_shim():
|
||||
try:
|
||||
sys.meta_path.remove(DISTUTILS_FINDER)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
if sys.version_info < (3, 12):
|
||||
# DistutilsMetaFinder can only be disabled in Python < 3.12 (PEP 632)
|
||||
remove_shim = _remove_shim
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
__import__('_distutils_hack').do_override()
|
||||
|
|
@ -0,0 +1 @@
|
|||
pip
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
Software License Agreement (BSD License)
|
||||
|
||||
Copyright (c) 2012, Willow Garage, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Willow Garage, Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: catkin-pkg
|
||||
Version: 1.1.0
|
||||
Summary: catkin package library
|
||||
Home-page: http://wiki.ros.org/catkin_pkg
|
||||
Author: Dirk Thomas
|
||||
Author-email: dthomas@osrfoundation.org
|
||||
Maintainer: ROS Infrastructure Team
|
||||
License: BSD
|
||||
Project-URL: Source code, https://github.com/ros-infrastructure/catkin_pkg
|
||||
Project-URL: Issue tracker, https://github.com/ros-infrastructure/catkin_pkg/issues
|
||||
Keywords: catkin,ROS
|
||||
Platform: UNKNOWN
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Requires-Python: >=3.6
|
||||
License-File: LICENSE
|
||||
Requires-Dist: docutils
|
||||
Requires-Dist: packaging
|
||||
Requires-Dist: python-dateutil
|
||||
Requires-Dist: pyparsing
|
||||
Requires-Dist: setuptools
|
||||
Provides-Extra: test
|
||||
Requires-Dist: flake8 ; extra == 'test'
|
||||
Requires-Dist: flake8-blind-except ; extra == 'test'
|
||||
Requires-Dist: flake8-builtins ; extra == 'test'
|
||||
Requires-Dist: flake8-class-newline ; extra == 'test'
|
||||
Requires-Dist: flake8-comprehensions ; extra == 'test'
|
||||
Requires-Dist: flake8-deprecated ; extra == 'test'
|
||||
Requires-Dist: flake8-docstrings ; extra == 'test'
|
||||
Requires-Dist: flake8-import-order ; extra == 'test'
|
||||
Requires-Dist: flake8-quotes ; extra == 'test'
|
||||
Requires-Dist: pytest ; extra == 'test'
|
||||
|
||||
Library for retrieving information about catkin packages.
|
||||
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
../../../bin/catkin_create_pkg,sha256=M_ds6oRFD60TjfzDqtwZ6sGqIHQrjjm-4i2o_D6r9Pc,244
|
||||
../../../bin/catkin_find_pkg,sha256=mRHI5q5ADxGy2BGI0hjUBxioU9nz_Eiit--LOlTyLCQ,242
|
||||
../../../bin/catkin_generate_changelog,sha256=NFNnTCj-pGgYlmJgSgSBrkma0wrtI67bG9MSF1fVJAQ,298
|
||||
../../../bin/catkin_package_version,sha256=rJd0dB-Z2qPcZmvqUxI3rCU61HJ_bs99zppKJIWvXF4,249
|
||||
../../../bin/catkin_prepare_release,sha256=2Tr-36V3M178Xs5OlAZLybPdSHCPM4JZTE0tOvykuUw,249
|
||||
../../../bin/catkin_tag_changelog,sha256=JIZb6B3ILUmwCQEfw5C1_fAySowOi4dUMKGSCmqMn78,247
|
||||
../../../bin/catkin_test_changelog,sha256=0fuUkixPk1mJXLjqlYTS7ZceToj4AIFaYbhMhz0UIA4,248
|
||||
catkin_pkg-1.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
catkin_pkg-1.1.0.dist-info/LICENSE,sha256=lFdqCndPGL3-oVEm5bibk1RyKW1yLseFK9gKnWL1sQM,1537
|
||||
catkin_pkg-1.1.0.dist-info/METADATA,sha256=gcHZijvnMW5Vjz4jlygcnEoFddQfy14rSNC8g9AqFKg,1279
|
||||
catkin_pkg-1.1.0.dist-info/RECORD,,
|
||||
catkin_pkg-1.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
catkin_pkg-1.1.0.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
|
||||
catkin_pkg-1.1.0.dist-info/entry_points.txt,sha256=xl2ewGfn3cllcxkeGAq2DV-OMsqMrrEF4DbCOLusuP0,445
|
||||
catkin_pkg-1.1.0.dist-info/top_level.txt,sha256=wdvMlyURP--dNAdiq2iS21TkEDJ41HEbD2gUdhFbW58,11
|
||||
catkin_pkg/__init__.py,sha256=vyLiPsA6TSEeZ9jDGOHt2w2fhJcgtT7hi_jM7v785bk,1741
|
||||
catkin_pkg/__pycache__/__init__.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/changelog.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/changelog_generator.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/changelog_generator_vcs.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/cmake.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/condition.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/group_dependency.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/group_membership.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/metapackage.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/package.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/package_templates.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/package_version.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/packages.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/python_setup.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/rospack.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/terminal_color.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/tool_detection.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/topological_order.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/workspace_vcs.cpython-312.pyc,,
|
||||
catkin_pkg/__pycache__/workspaces.cpython-312.pyc,,
|
||||
catkin_pkg/changelog.py,sha256=5_od_JkyB_OWWT5_To25DcEzT_7ygygboJAdIaNEFIY,19393
|
||||
catkin_pkg/changelog_generator.py,sha256=UCLWqe1c0BESxfZ5Dzs_KIVurVGnJFzvTQZd29tabFk,11908
|
||||
catkin_pkg/changelog_generator_vcs.py,sha256=YthNfrmLYTw-XqFEJODv6ht95hhnkQNgCKl43TKUrog,17184
|
||||
catkin_pkg/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
catkin_pkg/cli/__pycache__/__init__.cpython-312.pyc,,
|
||||
catkin_pkg/cli/__pycache__/create_pkg.cpython-312.pyc,,
|
||||
catkin_pkg/cli/__pycache__/find_pkg.cpython-312.pyc,,
|
||||
catkin_pkg/cli/__pycache__/generate_changelog.cpython-312.pyc,,
|
||||
catkin_pkg/cli/__pycache__/package_version.cpython-312.pyc,,
|
||||
catkin_pkg/cli/__pycache__/prepare_release.cpython-312.pyc,,
|
||||
catkin_pkg/cli/__pycache__/tag_changelog.cpython-312.pyc,,
|
||||
catkin_pkg/cli/__pycache__/test_changelog.cpython-312.pyc,,
|
||||
catkin_pkg/cli/create_pkg.py,sha256=AQ1YSjknVCE10t0Ccfk4cREP63PXJkmNWYNddFcvxEs,3098
|
||||
catkin_pkg/cli/find_pkg.py,sha256=nHaRbGOS8FMmAHx7POMJKdErMdWK0mUNyJfhW2gDrs0,946
|
||||
catkin_pkg/cli/generate_changelog.py,sha256=YRFwRl52I2vfmHD3IgEsGdn75lb9Hd3sISIz2OsZNYU,6762
|
||||
catkin_pkg/cli/package_version.py,sha256=mTWOhmFYIKS8ky_9UKZ8BxjKpm-x4RMpSiBTnZWeB4c,1618
|
||||
catkin_pkg/cli/prepare_release.py,sha256=rkSt67jSVDB2LSOn6xV955g6dW2JXE8oMqMNxhMH5K4,21319
|
||||
catkin_pkg/cli/tag_changelog.py,sha256=mWnt9kFXcJ-nJthSuP4oXujfaLg029NdhrFSpeH0lwg,5029
|
||||
catkin_pkg/cli/test_changelog.py,sha256=ou4hOuoTkj6NNdi3rlj4fjCu3_lJqNZH31EOxLLg4Hw,1613
|
||||
catkin_pkg/cmake.py,sha256=OIoTN6qBbnDfc0_YdC4PkDoqMmDm0Wm8QnfsxMDGbcc,3128
|
||||
catkin_pkg/condition.py,sha256=kv3Jam-78zc3mjmKppAquyAKX8eHVDdj8AuMEb4q_c8,4067
|
||||
catkin_pkg/group_dependency.py,sha256=eIkQlebfxK_I0fEGQo7bJgjyXlrd3S5ZzkAq3rNvCd4,2151
|
||||
catkin_pkg/group_membership.py,sha256=YGhSDCeFrOd7IDips_fnSE-KCNWbQuTwl7noYc-lnHI,1745
|
||||
catkin_pkg/metapackage.py,sha256=MKenO_thglQk03XyoNiRJpebxrayEqm9ZcA67MxhccM,6610
|
||||
catkin_pkg/package.py,sha256=D_OusuPjttLHEKE_gd20hwqcAABuehyFmxpkmmODT40,33666
|
||||
catkin_pkg/package_templates.py,sha256=8PxKwE_Q4SLu9KiPNzseXW0AuRDSjzypOkntCxSzjEw,18361
|
||||
catkin_pkg/package_version.py,sha256=xjmeJeLBVKU-74cBdKZs663-tFfDR9QAmlgHairiAyk,9006
|
||||
catkin_pkg/packages.py,sha256=ZgAQ8R10wlsn6vm1ss5F60dWnkqF_1Z41INRNMV1JxE,8294
|
||||
catkin_pkg/python_setup.py,sha256=vQEsbwtFo-Y21zDvj3Tx4fARG7BqLMFDk6OfluOEvu0,6113
|
||||
catkin_pkg/rospack.py,sha256=lZsyj7LJ4QncV96sp3319tSxQfZm7pwfmxFndvHTb80,1974
|
||||
catkin_pkg/templates/CMakeLists.txt.in,sha256=3iXhh5J2bC768ZmNdxbNet6Vzpj7rnFbdWw4CaDKZig,7028
|
||||
catkin_pkg/templates/metapackage.cmake.in,sha256=cPxwyD89WpnVNDGadMfFjHp38UNhlGYl_P8w31ZLrGk,128
|
||||
catkin_pkg/templates/package.xml.in,sha256=hipKerYFmlDftnuXOYVr-g4TvmfvMb8FcSqCJajQiOs,2423
|
||||
catkin_pkg/terminal_color.py,sha256=uzCmssQQvhQEyfzOTNCpIxt5G2EoUQPPJ8ZpExkvcKw,4281
|
||||
catkin_pkg/tool_detection.py,sha256=t-p-hGlxEAZwS8yi8POOwo18hQA_8-zKD4ne_O3bhYY,3209
|
||||
catkin_pkg/topological_order.py,sha256=T5LXlKDgQKzEVcnzOrfIenH6M0VdY-EXv388FiZBQ90,14195
|
||||
catkin_pkg/workspace_vcs.py,sha256=BFyZ7cIo4QKoa0awf6B1OW0_1MGVM7mJotEur_rx7xk,2698
|
||||
catkin_pkg/workspaces.py,sha256=h6OkKWBUU0kVGZAZd61WLfsSpYuyrPoB7VBV0OVbhjw,4613
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.34.2)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
[console_scripts]
|
||||
catkin_create_pkg = catkin_pkg.cli.create_pkg:main
|
||||
catkin_find_pkg = catkin_pkg.cli.find_pkg:main
|
||||
catkin_generate_changelog = catkin_pkg.cli.generate_changelog:main_catching_runtime_error
|
||||
catkin_package_version = catkin_pkg.cli.package_version:main
|
||||
catkin_prepare_release = catkin_pkg.cli.prepare_release:main
|
||||
catkin_tag_changelog = catkin_pkg.cli.tag_changelog:main
|
||||
catkin_test_changelog = catkin_pkg.cli.test_changelog:main
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
catkin_pkg
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2012, Willow Garage, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Willow Garage, Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived
|
||||
# from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Library for retrieving information about catkin packages."""
|
||||
|
||||
# same version as in:
|
||||
# - setup.py
|
||||
# - stdeb.cfg
|
||||
__version__ = '1.1.0'
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,567 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2013, Open Source Robotics Foundation, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Open Source Robotics Foundation, Inc. nor
|
||||
# the names of its contributors may be used to endorse or promote
|
||||
# products derived from this software without specific prior
|
||||
# written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
Processes ROS changelogs so that they can be used in binary packaging.
|
||||
|
||||
The Changelog format is described in REP-0132:
|
||||
|
||||
http://ros.org/reps/rep-0132.html
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import dateutil.parser
|
||||
import docutils
|
||||
import docutils.core
|
||||
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
_py3 = sys.version_info[0] >= 3
|
||||
|
||||
try:
|
||||
_unicode = unicode
|
||||
except NameError:
|
||||
_unicode = str
|
||||
|
||||
__author__ = 'William Woodall'
|
||||
__email__ = 'william@osrfoundation.org'
|
||||
__maintainer__ = 'William Woodall'
|
||||
|
||||
log = logging.getLogger('changelog')
|
||||
|
||||
CHANGELOG_FILENAME = 'CHANGELOG.rst'
|
||||
|
||||
example_rst = """\
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Changelog for package foo
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
0.1
|
||||
===
|
||||
Free form text about this minor release.
|
||||
|
||||
0.1.27 (forthcoming)
|
||||
--------------------
|
||||
* Great new feature
|
||||
|
||||
0.1.26 (2012-12-26)
|
||||
-------------------
|
||||
* Utilizes caching to improve query performance (fix https://github.com/ros/ros_comm/pull/2)
|
||||
* Simplified API calls based on (https://github.com/ros/robot_model):
|
||||
|
||||
* Note that these changes are based on REP 192
|
||||
* Also they fix a problem related to initialization
|
||||
|
||||
* Fixed synchronization issue on startup
|
||||
|
||||
.. not mentioning secret feature on purpose
|
||||
|
||||
0.1.25 (2012-11-25)
|
||||
-------------------
|
||||
|
||||
- Added thread safety
|
||||
- Replaced custom XML parser with `TinyXML <http://www.grinninglizard.com/tinyxml/>`_.
|
||||
- Fixed regression introduced in 0.1.22
|
||||
- New syntax for foo::
|
||||
|
||||
foo('bar')
|
||||
|
||||
- Added a safety check for XML parsing
|
||||
|
||||
----
|
||||
|
||||
The library should now compile under ``Win32``
|
||||
|
||||
0.1.0 (2012-10-01)
|
||||
------------------
|
||||
|
||||
*First* public **stable** release
|
||||
|
||||
0.0
|
||||
===
|
||||
|
||||
0.0.1 (2012-01-31)
|
||||
------------------
|
||||
|
||||
1. Initial release
|
||||
2. Initial bugs
|
||||
"""
|
||||
|
||||
|
||||
def bullet_list_class_from_docutils(bullet_list, bullet_type=None):
|
||||
"""
|
||||
Process elements of bullet list into an encapsulating class.
|
||||
|
||||
:param bullet_list: ``docutils.nodes.bullet_list`` list to be processed
|
||||
:param bullet_type: ``str`` either 'bullet' or 'enumerated'
|
||||
:returns: ``BulletList`` object representing a docutils bullet_list
|
||||
"""
|
||||
content = BulletList(bullet_type=bullet_type)
|
||||
for child in bullet_list.children:
|
||||
if isinstance(child, docutils.nodes.list_item):
|
||||
content.bullets.append(mixed_text_from_docutils(child))
|
||||
else:
|
||||
log.debug("Skipped bullet_list child: '{0}'".format(child))
|
||||
return content
|
||||
|
||||
|
||||
def mixed_text_from_docutils(node):
|
||||
"""
|
||||
Take most Text-ish docutils objects and converts them to MixedText.
|
||||
|
||||
:param node: ``docutils.nodes.{paragraph, list_item, ...}`` text-ish
|
||||
:returns: ``MixedText`` representing the given docutils object
|
||||
"""
|
||||
content = MixedText()
|
||||
for child in node.children:
|
||||
if isinstance(child, docutils.nodes.paragraph):
|
||||
content.texts.extend(mixed_text_from_docutils(child).texts)
|
||||
elif isinstance(child, docutils.nodes.Text):
|
||||
content.texts.append(child.astext())
|
||||
elif isinstance(child, docutils.nodes.reference):
|
||||
content.texts.append(reference_from_docutils(child))
|
||||
elif isinstance(child, docutils.nodes.emphasis):
|
||||
content.texts.append('*{0}*'.format(child.astext()))
|
||||
elif isinstance(child, docutils.nodes.strong):
|
||||
content.texts.append('**{0}**'.format(child.astext()))
|
||||
elif isinstance(child, docutils.nodes.literal):
|
||||
content.texts.append('``{0}``'.format(child.astext()))
|
||||
elif isinstance(child, docutils.nodes.literal_block):
|
||||
content.texts.append('\n\n ' + child.astext() + '\n')
|
||||
elif isinstance(child, docutils.nodes.target):
|
||||
pass
|
||||
elif isinstance(child, docutils.nodes.system_message):
|
||||
log.debug('Skipping system_message: {0}'.format(child))
|
||||
elif isinstance(child, docutils.nodes.bullet_list):
|
||||
content.texts.append(bullet_list_class_from_docutils(child))
|
||||
else:
|
||||
try:
|
||||
# Try to add it as plain text
|
||||
log.debug("Trying to add {0}'s child of type {1}: '{2}'"
|
||||
.format(type(node), type(child), child))
|
||||
content.texts.append(child.astext())
|
||||
except AttributeError:
|
||||
log.debug("Ignored {0} child of type {1}: '{2}'"
|
||||
.format(type(node), type(child), child))
|
||||
return content
|
||||
|
||||
|
||||
def get_changelog_from_path(path, package_name=None):
|
||||
"""
|
||||
Changelog factory, which reads a changelog file into a class.
|
||||
|
||||
:param path: ``str`` the path of the changelog including or excluding the filename CHANGELOG.rst
|
||||
:param package_name: ``str`` the package name
|
||||
:returns: ``Changelog`` changelog class or None if file was not readable
|
||||
"""
|
||||
changelog = Changelog(package_name)
|
||||
if os.path.isdir(path):
|
||||
path = os.path.join(path, CHANGELOG_FILENAME)
|
||||
try:
|
||||
with open(path, 'rb') as f:
|
||||
populate_changelog_from_rst(changelog, f.read().decode('utf-8'))
|
||||
except IOError:
|
||||
return None
|
||||
return changelog
|
||||
|
||||
|
||||
def populate_changelog_from_rst(changelog, rst):
|
||||
"""
|
||||
Changelog factory, which converts the raw ReST into a class.
|
||||
|
||||
:param changelog: ``Changelog`` changelog to be populated
|
||||
:param rst: ``str`` raw ReST changelog
|
||||
:returns: ``Changelog`` changelog that was populated
|
||||
"""
|
||||
document = docutils.core.publish_doctree(rst)
|
||||
processes_changelog_children(changelog, document.children)
|
||||
changelog.rst = rst
|
||||
return changelog
|
||||
|
||||
|
||||
def processes_changelog_children(changelog, children):
|
||||
"""
|
||||
Process docutils children into a REP-0132 changelog instance.
|
||||
|
||||
Recurse into sections, check (sub-)titles if they are valid versions.
|
||||
|
||||
:param changelog: ``Changelog`` changelog to be populated
|
||||
:param section: ``docutils.nodes.section`` section to be processed
|
||||
:returns: ``Changelog`` changelog that was populated
|
||||
"""
|
||||
for i, child in enumerate(children):
|
||||
if isinstance(child, docutils.nodes.section):
|
||||
processes_changelog_children(changelog, child.children)
|
||||
elif isinstance(child, docutils.nodes.title) or isinstance(child, docutils.nodes.subtitle):
|
||||
version, date = None, None
|
||||
# See if the title has a text element in it
|
||||
if len(child.children) > 0 and any(isinstance(c, docutils.nodes.Text) for c in child.traverse()):
|
||||
# Extract version and date from (sub-)title
|
||||
title_text = child.astext()
|
||||
try:
|
||||
version, date = version_and_date_from_title(title_text)
|
||||
except InvalidSectionTitle:
|
||||
# Catch invalid section titles
|
||||
log.debug("Ignored non-compliant title: '{0}'".format(title_text))
|
||||
continue
|
||||
valid_section = None not in (version, date)
|
||||
if valid_section:
|
||||
contents = []
|
||||
# For each remaining sibling
|
||||
for child in children[i + 1:]:
|
||||
# Skip sections (nesting of valid sections not allowed)
|
||||
if isinstance(child, docutils.nodes.section):
|
||||
log.debug("Ignored section child: '{0}'".format(child))
|
||||
continue
|
||||
# Skip title
|
||||
if isinstance(child, docutils.nodes.title):
|
||||
continue
|
||||
# Skip comments
|
||||
if isinstance(child, docutils.nodes.comment):
|
||||
log.debug("Ignored section child: '{0}'".format(child))
|
||||
continue
|
||||
# Process other elements into the contents
|
||||
if isinstance(child, docutils.nodes.bullet_list):
|
||||
contents.append(bullet_list_class_from_docutils(child))
|
||||
elif isinstance(child, docutils.nodes.enumerated_list):
|
||||
contents.append(bullet_list_class_from_docutils(child, bullet_type='enumerated'))
|
||||
elif isinstance(child, docutils.nodes.transition):
|
||||
contents.append(Transition())
|
||||
elif isinstance(child, docutils.nodes.paragraph):
|
||||
contents.append(mixed_text_from_docutils(child))
|
||||
else:
|
||||
log.debug("Skipped section child: '{0}'".format(child))
|
||||
changelog.add_version_section(version, date, contents)
|
||||
break
|
||||
else:
|
||||
log.debug("Ignored non-compliant title: '{0}'".format(child))
|
||||
|
||||
|
||||
def reference_from_docutils(reference):
|
||||
"""
|
||||
Turn a reference element into a ``Reference``.
|
||||
|
||||
:param reference: ``docutils.nodes.reference`` reference element
|
||||
:returns: ``Reference`` simpler object representing the reference
|
||||
"""
|
||||
name, refuri = None, None
|
||||
for pair in reference.attlist():
|
||||
if pair[0] == 'name':
|
||||
name = pair[1]
|
||||
if pair[0] == 'refuri':
|
||||
refuri = pair[1]
|
||||
return Reference(name, refuri)
|
||||
|
||||
|
||||
def version_and_date_from_title(title):
|
||||
"""
|
||||
Split a section title into version and date if possible.
|
||||
|
||||
:param title: ``str`` raw section title to be processed
|
||||
:returns: ``(str, datetime.datetime)``
|
||||
:raises: ``InvalidSectionTitle`` for non REP-0132 section titles
|
||||
"""
|
||||
match = re.search(r'^([0-9]+\.[0-9]+\.[0-9]+)[ ]\((.+)\)$', title)
|
||||
if match is None:
|
||||
raise InvalidSectionTitle(title)
|
||||
version, date_str = match.groups()
|
||||
try:
|
||||
date = dateutil.parser.parse(date_str)
|
||||
except (ValueError, TypeError) as e:
|
||||
# Catch invalid dates
|
||||
log.debug("Error parsing date ({0}): '{1}'".format(date_str, e))
|
||||
raise InvalidSectionTitle(title)
|
||||
return version, date
|
||||
|
||||
|
||||
class BulletList(object):
|
||||
"""Represent a bulleted list of text."""
|
||||
|
||||
def __init__(self, bullets=None, bullet_type=None):
|
||||
"""
|
||||
Initialize BulletList.
|
||||
|
||||
:param bullets: ``list(MixedText)`` list of text bullets
|
||||
:param bullet_type: ``str`` either 'bullet' or 'enumerated'
|
||||
"""
|
||||
bullet_type = 'bullet' if bullet_type is None else bullet_type
|
||||
if bullet_type not in ['bullet', 'enumerated']:
|
||||
raise RuntimeError("Invalid bullet type: '{0}'".format(bullet_type))
|
||||
self.bullets = bullets or []
|
||||
self.bullet_type = bullet_type
|
||||
|
||||
def __iter__(self):
|
||||
for bullet in self.bullets:
|
||||
yield bullet
|
||||
|
||||
def __str__(self):
|
||||
value = self.__unicode__()
|
||||
if not _py3:
|
||||
value = value.encode('ascii', 'replace')
|
||||
return value
|
||||
|
||||
def __unicode__(self):
|
||||
return self.as_txt()
|
||||
|
||||
def as_rst(self):
|
||||
return self.as_txt(indent='', use_hyphen_bullet=True)
|
||||
|
||||
def as_txt(self, indent='', use_hyphen_bullet=False):
|
||||
bullet = '*' if self.bullet_type == 'bullet' else '#'
|
||||
if use_hyphen_bullet and bullet == '*':
|
||||
bullet = '-'
|
||||
b = self.bullet_generator(bullet)
|
||||
i = indent
|
||||
n = '\n' + i + ' '
|
||||
lines = [i + next(b) + _unicode(item).replace('\n', n) for item in self]
|
||||
return '\n'.join(lines)
|
||||
|
||||
def bullet_generator(self, bullet):
|
||||
if '#' == bullet:
|
||||
bullets = [str(i) + '. ' for i in range(1, len(self.bullets) + 1)]
|
||||
else:
|
||||
bullets = [bullet + ' '] * len(self.bullets)
|
||||
for b in bullets:
|
||||
yield b
|
||||
|
||||
|
||||
class Changelog(object):
|
||||
"""Represents a REP-0132 changelog."""
|
||||
|
||||
def __init__(self, package_name=None):
|
||||
self.__package_name = package_name
|
||||
self.__versions = []
|
||||
self.__parsed_versions = []
|
||||
self.__dates = {}
|
||||
self.__content = {}
|
||||
self.__rst = ''
|
||||
|
||||
def __str__(self):
|
||||
value = self.__unicode__()
|
||||
if not _py3:
|
||||
value = value.encode('ascii', 'replace')
|
||||
return value
|
||||
|
||||
def __unicode__(self):
|
||||
msg = []
|
||||
if self.__package_name:
|
||||
msg.append("Changelog for package '{0}'".format(self.package_name))
|
||||
for version, date, content in self.foreach_version(reverse=True):
|
||||
msg.append(' ' + version + ' ({0}):'.format(date))
|
||||
for item in content:
|
||||
msg.extend([' ' + i for i in _unicode(item).splitlines()])
|
||||
return '\n'.join(msg)
|
||||
|
||||
@property
|
||||
def package_name(self):
|
||||
return self.__package_name
|
||||
|
||||
@package_name.setter
|
||||
def package_name(self, package_name):
|
||||
self.__package_name = package_name
|
||||
|
||||
@property
|
||||
def rst(self):
|
||||
return self.__rst
|
||||
|
||||
@rst.setter
|
||||
def rst(self, rst):
|
||||
self.__rst = rst
|
||||
|
||||
def add_version_section(self, version, date, contents):
|
||||
"""
|
||||
Add a version section.
|
||||
|
||||
:param version: ``str`` version as a string
|
||||
:param date: ``datetime.datetime`` version date
|
||||
:param contents: ``list(list([str|Reference]))``` contents as a list
|
||||
of lists which contain a combination of ``str`` and
|
||||
``Reference`` objects
|
||||
:returns: None
|
||||
"""
|
||||
if version in self.__versions:
|
||||
raise DuplicateVersionsException(version)
|
||||
self.__parsed_versions.append(parse_version(version))
|
||||
self.__parsed_versions = sorted(self.__parsed_versions)
|
||||
# Cannot go parsed -> str, so sorting must be done by comparison
|
||||
new_versions = [None] * len(self.__parsed_versions)
|
||||
for v in self.__versions + [version]:
|
||||
parsed_v = parse_version(v)
|
||||
index = self.__parsed_versions.index(parsed_v)
|
||||
if index == -1:
|
||||
raise RuntimeError('Inconsistent internal version storage state')
|
||||
new_versions[index] = v
|
||||
self.__versions = new_versions
|
||||
self.__dates[version] = date
|
||||
self.__content[version] = contents
|
||||
|
||||
def foreach_version(self, reverse=False):
|
||||
"""
|
||||
Create a generator for iterating over the versions, dates and content.
|
||||
|
||||
Versions are stored and iterated in order.
|
||||
|
||||
:param reverse: ``bool`` if True then the iteration is reversed
|
||||
:returns: ``generator`` for iterating over versions, dates and content
|
||||
"""
|
||||
for version in reversed(self.__versions) if reverse else self.__versions:
|
||||
yield version, self.__dates[version], self.__content[version]
|
||||
|
||||
def get_date_of_version(self, version):
|
||||
"""Return date of a given version as a ``datetime.datetime``."""
|
||||
if version not in self.__versions:
|
||||
raise KeyError("No date for version '{0}'".format(version))
|
||||
return self.__dates[version]
|
||||
|
||||
def get_content_of_version(self, version):
|
||||
"""
|
||||
Return changelog content for a given version.
|
||||
|
||||
:param version: ``str`` version
|
||||
:returns: ``list(list([str|Reference]))`` content expanded
|
||||
"""
|
||||
if version not in self.__versions:
|
||||
raise KeyError("No content for version '{0}'".format(version))
|
||||
return self.__content[version]
|
||||
|
||||
|
||||
class DuplicateVersionsException(Exception):
|
||||
"""Raised when more than one section per version is given."""
|
||||
|
||||
def __init__(self, version):
|
||||
self.version = version
|
||||
Exception.__init__(self, "Version '{0}' is specified twice".format(version))
|
||||
|
||||
|
||||
class InvalidSectionTitle(Exception):
|
||||
"""raised on non REP-0132 section titles."""
|
||||
|
||||
def __init__(self, title):
|
||||
self.title = title
|
||||
msg = "Section title does not conform to REP-0132: '{0}'".format(title)
|
||||
Exception.__init__(self, msg)
|
||||
|
||||
|
||||
class MixedText(object):
|
||||
"""Represents text mixed with references and nested bullets."""
|
||||
|
||||
def __init__(self, texts=[]):
|
||||
self.texts = list(texts)
|
||||
|
||||
def __iter__(self):
|
||||
for text in self.texts:
|
||||
yield text
|
||||
|
||||
def __str__(self):
|
||||
value = self.__unicode__()
|
||||
if not _py3:
|
||||
value = value.encode('ascii', 'replace')
|
||||
return value
|
||||
|
||||
def __unicode__(self):
|
||||
return self.to_txt()
|
||||
|
||||
def to_txt(self, bullet_indent=' '):
|
||||
lines = []
|
||||
for t in self:
|
||||
if isinstance(t, BulletList):
|
||||
bullets = [bullet_indent + x for x in _unicode(t).splitlines()]
|
||||
bullets = ['', ''] + bullets + ['']
|
||||
lines.extend('\n'.join(bullets))
|
||||
else:
|
||||
lines.append(_unicode(t))
|
||||
return ''.join(lines)
|
||||
|
||||
|
||||
class Reference(object):
|
||||
"""Represents a piece of text with an associated link."""
|
||||
|
||||
def __init__(self, text, link):
|
||||
self.text = text
|
||||
self.link = link
|
||||
|
||||
def __str__(self):
|
||||
value = self.__unicode__()
|
||||
if not _py3:
|
||||
value = value.encode('ascii', 'replace')
|
||||
return value
|
||||
|
||||
def __unicode__(self):
|
||||
return self.as_txt()
|
||||
|
||||
def as_rst(self):
|
||||
"""Self as rst (unicode)."""
|
||||
if self.text is None:
|
||||
return _unicode(self.link)
|
||||
return '`{0} <{1}>`_'.format(self.text, self.link)
|
||||
|
||||
def as_txt(self):
|
||||
"""Self formatted for plain text (unicode)."""
|
||||
if self.text is None:
|
||||
return _unicode(self.link)
|
||||
return '{0} <{1}>'.format(self.text, self.link)
|
||||
|
||||
|
||||
class Transition(object):
|
||||
"""Represents a trasition element from ReST."""
|
||||
|
||||
def __str__(self):
|
||||
value = self.__unicode__()
|
||||
if not _py3:
|
||||
value = value.encode('ascii', 'replace')
|
||||
return value
|
||||
|
||||
def __unicode__(self):
|
||||
return '-' * 20
|
||||
|
||||
def __iter__(self):
|
||||
yield self.unicode()
|
||||
|
||||
|
||||
def __test():
|
||||
package_name = 'foo'
|
||||
changelog = Changelog(package_name)
|
||||
print(populate_changelog_from_rst(changelog, example_rst))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig()
|
||||
log.setLevel(logging.DEBUG)
|
||||
__test()
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2013, Open Source Robotics Foundation, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Open Source Robotics Foundation, Inc. nor
|
||||
# the names of its contributors may be used to endorse or promote
|
||||
# products derived from this software without specific prior
|
||||
# written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
Generate/update ROS changelog files.
|
||||
|
||||
The Changelog format is described in REP-0132:
|
||||
|
||||
http://ros.org/reps/rep-0132.html
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from catkin_pkg.changelog import CHANGELOG_FILENAME
|
||||
from catkin_pkg.changelog_generator_vcs import Tag
|
||||
|
||||
FORTHCOMING_LABEL = 'Forthcoming'
|
||||
|
||||
|
||||
def get_all_changes(vcs_client, skip_merges=False, only_merges=False):
|
||||
tags = _get_version_tags(vcs_client)
|
||||
|
||||
# query all log entries per tag range
|
||||
tag2log_entries = {}
|
||||
previous_tag = Tag(None)
|
||||
for tag in sorted_tags(tags):
|
||||
log_entries = vcs_client.get_log_entries(
|
||||
from_tag=previous_tag.name, to_tag=tag.name, skip_merges=skip_merges, only_merges=only_merges)
|
||||
tag2log_entries[previous_tag] = log_entries
|
||||
previous_tag = tag
|
||||
log_entries = vcs_client.get_log_entries(
|
||||
from_tag=previous_tag.name, to_tag=None, skip_merges=skip_merges, only_merges=only_merges)
|
||||
tag2log_entries[previous_tag] = log_entries
|
||||
return tag2log_entries
|
||||
|
||||
|
||||
def get_forthcoming_changes(vcs_client, skip_merges=False, only_merges=False):
|
||||
tags = _get_version_tags(vcs_client)
|
||||
latest_tag_name = _get_latest_version_tag_name(vcs_client)
|
||||
|
||||
# query log entries since latest tag only
|
||||
tag2log_entries = {}
|
||||
from_tag = Tag(None)
|
||||
to_tag = Tag(latest_tag_name)
|
||||
for tag in sorted_tags(tags):
|
||||
if to_tag.name is None:
|
||||
to_tag = tag
|
||||
# ignore non-forthcoming log entries but keep version to identify injection point of forthcoming
|
||||
tag2log_entries[tag] = None
|
||||
log_entries = vcs_client.get_log_entries(
|
||||
from_tag=from_tag.name, to_tag=to_tag.name, skip_merges=skip_merges, only_merges=only_merges)
|
||||
tag2log_entries[from_tag] = log_entries
|
||||
return tag2log_entries
|
||||
|
||||
|
||||
def _get_version_tags(vcs_client):
|
||||
# get all tags in descending order
|
||||
tags = vcs_client.get_tags()
|
||||
version_tags = [t for t in tags if re.match(r'^v?\d+\.\d+.\d+$', t.name)]
|
||||
return version_tags
|
||||
|
||||
|
||||
def _get_latest_version_tag_name(vcs_client):
|
||||
# get latest tag
|
||||
tag_name = vcs_client.get_latest_tag_name()
|
||||
if not re.match(r'^v?\d+\.\d+.\d+$', tag_name):
|
||||
raise RuntimeError(
|
||||
"The tag name '{}' doesn't match the version pattern v?x.y.z".format(tag_name))
|
||||
return tag_name
|
||||
|
||||
|
||||
def generate_changelogs(base_path, packages, tag2log_entries, logger=None, vcs_client=None, skip_contributors=False):
|
||||
for pkg_path, package in packages.items():
|
||||
changelog_path = os.path.join(base_path, pkg_path, CHANGELOG_FILENAME)
|
||||
if os.path.exists(changelog_path):
|
||||
continue
|
||||
# generate package specific changelog file
|
||||
if logger:
|
||||
logger.debug("- creating '%s'" % os.path.join(pkg_path, CHANGELOG_FILENAME))
|
||||
pkg_tag2log_entries = filter_package_changes(tag2log_entries, pkg_path)
|
||||
data = generate_changelog_file(package.name, pkg_tag2log_entries, vcs_client=vcs_client, skip_contributors=skip_contributors)
|
||||
with open(changelog_path, 'wb') as f:
|
||||
f.write(data.encode('utf-8'))
|
||||
|
||||
|
||||
def update_changelogs(base_path, packages, tag2log_entries, logger=None, vcs_client=None, skip_contributors=False):
|
||||
for pkg_path in packages.keys():
|
||||
# update package specific changelog file
|
||||
if logger:
|
||||
logger.debug("- updating '%s'" % os.path.join(pkg_path, CHANGELOG_FILENAME))
|
||||
pkg_tag2log_entries = filter_package_changes(tag2log_entries, pkg_path)
|
||||
changelog_path = os.path.join(base_path, pkg_path, CHANGELOG_FILENAME)
|
||||
with open(changelog_path, 'rb') as f:
|
||||
data = f.read().decode('utf-8')
|
||||
data = update_changelog_file(data, pkg_tag2log_entries, vcs_client=vcs_client, skip_contributors=skip_contributors)
|
||||
with open(changelog_path, 'wb') as f:
|
||||
f.write(data.encode('utf-8'))
|
||||
|
||||
|
||||
def filter_package_changes(tag2log_entries, pkg_path):
|
||||
pkg_tag2log_entries = {}
|
||||
# collect all log entries relevant for this package
|
||||
for tag, log_entries in tag2log_entries.items():
|
||||
if log_entries is None:
|
||||
pkg_log_entries = None
|
||||
else:
|
||||
pkg_log_entries = []
|
||||
for log_entry in log_entries:
|
||||
if log_entry.affects_path(pkg_path):
|
||||
pkg_log_entries.append(log_entry)
|
||||
pkg_tag2log_entries[tag] = pkg_log_entries
|
||||
return pkg_tag2log_entries
|
||||
|
||||
|
||||
def generate_changelog_file(pkg_name, tag2log_entries, vcs_client=None, skip_contributors=False):
|
||||
blocks = []
|
||||
blocks.append(generate_package_headline(pkg_name))
|
||||
|
||||
for tag in sorted_tags(tag2log_entries.keys()):
|
||||
log_entries = tag2log_entries[tag]
|
||||
if log_entries is not None:
|
||||
blocks.append(generate_version_block(version_from_tag(tag.name), tag.timestamp, log_entries, vcs_client=vcs_client, skip_contributors=skip_contributors))
|
||||
|
||||
return '\n'.join(blocks)
|
||||
|
||||
|
||||
def update_changelog_file(data, tag2log_entries, vcs_client=None, skip_contributors=False):
|
||||
tags = sorted_tags(tag2log_entries.keys())
|
||||
for i, tag in enumerate(tags):
|
||||
log_entries = tag2log_entries[tag]
|
||||
if log_entries is None:
|
||||
continue
|
||||
content = generate_version_content(log_entries, vcs_client=vcs_client, skip_contributors=skip_contributors)
|
||||
|
||||
# check if version section exists
|
||||
match = get_version_section_match(data, version_from_tag(tag.name))
|
||||
if match:
|
||||
# prepend content to existing section
|
||||
data = prepend_version_content(data, version_from_tag(tag.name), content)
|
||||
assert data is not None
|
||||
else:
|
||||
# find injection point of earliest following version
|
||||
for next_tag in list(tags)[i:]:
|
||||
match = get_version_section_match(data, version_from_tag(next_tag.name))
|
||||
if match:
|
||||
block = generate_version_block(version_from_tag(tag.name), tag.timestamp, log_entries, vcs_client=vcs_client, skip_contributors=skip_contributors)
|
||||
data = data[:match.start()] + block + '\n' + data[match.start():]
|
||||
break
|
||||
if not match:
|
||||
if tag.name is None:
|
||||
raise RuntimeError('Could not find section "%s"' % next_tag.name)
|
||||
else:
|
||||
raise RuntimeError('Could neither find section "%s" nor any other section' % tag.name)
|
||||
return data
|
||||
|
||||
|
||||
def get_version_section_match(data, version):
|
||||
pattern = get_version_section_pattern(version)
|
||||
matches = re.finditer(pattern, data, flags=re.MULTILINE)
|
||||
matches = list(matches)
|
||||
if len(matches) > 1:
|
||||
raise RuntimeError('Found multiple matching sections')
|
||||
return matches[0] if matches else None
|
||||
|
||||
|
||||
def get_version_section_pattern(version):
|
||||
valid_section_characters = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
|
||||
headline = get_version_headline(version, None)
|
||||
pattern = '^(' + re.escape(headline) + r'( \([0-9 \-:|+]+\))?)\r?\n([' + re.escape(valid_section_characters) + ']+)\r?\n?$'
|
||||
return pattern
|
||||
|
||||
|
||||
def prepend_version_content(data, version, content):
|
||||
pattern = get_version_section_pattern(version)
|
||||
|
||||
def replace_section(match):
|
||||
headline = match.group(1)
|
||||
section = match.group(3)
|
||||
data = content.rstrip()
|
||||
if data:
|
||||
data += '\n'
|
||||
return headline + '\n' + section + '\n' + data
|
||||
|
||||
data, count = re.subn(pattern, replace_section, data, flags=re.MULTILINE)
|
||||
if count > 1:
|
||||
raise RuntimeError('Found multiple matching sections')
|
||||
return data if count == 1 else None
|
||||
|
||||
|
||||
def version_from_tag(tag_name):
|
||||
if tag_name is None:
|
||||
return None
|
||||
if tag_name.startswith('v'):
|
||||
return tag_name[1:]
|
||||
return tag_name
|
||||
|
||||
|
||||
def sorted_tags(tags):
|
||||
# first return the forthcoming tag
|
||||
for tag in tags:
|
||||
if not tag.name:
|
||||
yield tag
|
||||
# then return the tags in descending order
|
||||
name_and_tag = [(t.name, t) for t in tags if t.name]
|
||||
name_and_tag.sort(key=lambda x: [int(y) for y in version_from_tag(x[0]).split('.')])
|
||||
name_and_tag.reverse()
|
||||
for (_, tag) in name_and_tag:
|
||||
yield tag
|
||||
|
||||
|
||||
def generate_package_headline(pkg_name):
|
||||
headline = 'Changelog for package %s' % pkg_name
|
||||
section_marker = '^' * len(headline)
|
||||
return '%s\n%s\n%s\n' % (section_marker, headline, section_marker)
|
||||
|
||||
|
||||
def generate_version_block(version, timestamp, log_entries, vcs_client=None, skip_contributors=False):
|
||||
data = generate_version_headline(version, timestamp)
|
||||
data += generate_version_content(log_entries, vcs_client=vcs_client, skip_contributors=skip_contributors)
|
||||
return data
|
||||
|
||||
|
||||
def generate_version_headline(version, timestamp):
|
||||
headline = get_version_headline(version, timestamp)
|
||||
return '%s\n%s\n' % (headline, '-' * len(headline))
|
||||
|
||||
|
||||
def get_version_headline(version, timestamp):
|
||||
if not version:
|
||||
return FORTHCOMING_LABEL
|
||||
headline = version
|
||||
if timestamp:
|
||||
headline += ' (%s)' % timestamp
|
||||
return headline
|
||||
|
||||
|
||||
def generate_version_content(log_entries, vcs_client=None, skip_contributors=False):
|
||||
data = ''
|
||||
all_authors = set()
|
||||
for entry in log_entries:
|
||||
msg = entry.msg
|
||||
lines = msg.splitlines()
|
||||
lines = [line.strip() for line in lines]
|
||||
lines = [line for line in lines if line and not line.startswith('Signed-off-by:')]
|
||||
lines = [escape_trailing_underscores(line) for line in lines]
|
||||
data += '* %s\n' % (replace_repository_references(lines[0], vcs_client=vcs_client) if lines else '')
|
||||
for line in lines[1:]:
|
||||
data += ' %s\n' % replace_repository_references(line, vcs_client=vcs_client)
|
||||
all_authors.add(entry.author)
|
||||
if all_authors and not skip_contributors:
|
||||
data += '* Contributors: %s\n' % ', '.join(sorted(all_authors))
|
||||
return data
|
||||
|
||||
|
||||
def escape_trailing_underscores(line):
|
||||
if line.endswith('_'):
|
||||
line = line[:-1] + r'\_'
|
||||
# match words ending with an underscore which are not followed by another word
|
||||
# and insert a backslash before the underscore to escape it
|
||||
line = re.sub(r'(\w+)_([^\w])', '\\1\\_\\2', line)
|
||||
return line
|
||||
|
||||
|
||||
def replace_repository_references(line, vcs_client=None):
|
||||
if vcs_client:
|
||||
line = vcs_client.replace_repository_references(line)
|
||||
return line
|
||||
|
|
@ -0,0 +1,411 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2013, Open Source Robotics Foundation, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Open Source Robotics Foundation, Inc. nor
|
||||
# the names of its contributors may be used to endorse or promote
|
||||
# products derived from this software without specific prior
|
||||
# written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Extract log information from repositories."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
try:
|
||||
from shutil import which
|
||||
except ImportError:
|
||||
# fallback for Python < 3.3
|
||||
def which(cmd):
|
||||
for path in os.getenv('PATH').split(os.path.pathsep):
|
||||
file_path = os.path.join(path, cmd)
|
||||
if os.path.isfile(file_path):
|
||||
return file_path
|
||||
return None
|
||||
|
||||
|
||||
class Tag(object):
|
||||
|
||||
def __init__(self, name, timestamp=None):
|
||||
self.name = name
|
||||
self.timestamp = timestamp
|
||||
|
||||
|
||||
class LogEntry(object):
|
||||
|
||||
def __init__(self, msg, affected_paths, author):
|
||||
self.msg = msg
|
||||
self.author = author
|
||||
self._affected_paths = [p for p in affected_paths if p]
|
||||
|
||||
def affects_path(self, path):
|
||||
for apath in self._affected_paths:
|
||||
# if the path is the root of the repository
|
||||
# it is affected by all changes
|
||||
if path == '.':
|
||||
return True
|
||||
if apath.startswith(os.path.join(path, '')):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class VcsClientBase(object):
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def get_tags(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_latest_tag_name(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_log_entries(self, from_tag, to_tag, skip_merges=False, only_merges=False):
|
||||
raise NotImplementedError()
|
||||
|
||||
def replace_repository_references(self, line):
|
||||
return line
|
||||
|
||||
def _run_command(self, cmd, env=None):
|
||||
cwd = os.path.abspath(self.path)
|
||||
result = {'cmd': ' '.join(cmd), 'cwd': cwd}
|
||||
try:
|
||||
proc = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
|
||||
output, _ = proc.communicate()
|
||||
result['output'] = output.rstrip().decode('utf-8')
|
||||
result['returncode'] = proc.returncode
|
||||
except subprocess.CalledProcessError as e:
|
||||
result['output'] = e.output
|
||||
result['returncode'] = e.returncode
|
||||
return result
|
||||
|
||||
def _truncate_timestamps(self, tags):
|
||||
# truncate timestamps to shortest unique representation
|
||||
# - date only
|
||||
# - date including hours and minutes
|
||||
# - date include hours, minutes and seconds
|
||||
lengths = [10, 16, 19]
|
||||
for length in lengths:
|
||||
# filter tags which have not been truncated yet
|
||||
considered_tags = [t for t in tags if len(t.timestamp) > length]
|
||||
# count tags which timestamps have the same truncated representation
|
||||
grouped_by_timestamp = {}
|
||||
for t in considered_tags:
|
||||
truncated_timestamp = t.timestamp[:length]
|
||||
if truncated_timestamp not in grouped_by_timestamp:
|
||||
grouped_by_timestamp[truncated_timestamp] = []
|
||||
grouped_by_timestamp[truncated_timestamp].append(t)
|
||||
# truncate timestamp of tags which are unique
|
||||
for truncated_timestamp, similar_tags in grouped_by_timestamp.items():
|
||||
if len(similar_tags) == 1:
|
||||
similar_tags[0].timestamp = truncated_timestamp
|
||||
|
||||
|
||||
class GitClient(VcsClientBase):
|
||||
|
||||
type = 'git' # noqa: A003
|
||||
|
||||
def __init__(self, path):
|
||||
super(GitClient, self).__init__(path)
|
||||
self._executable = which('git')
|
||||
self._repo_hosting = None
|
||||
self._github_base_url = 'https://github.com/'
|
||||
self._github_path = None
|
||||
self._gitlab_base_url = 'https://gitlab.com/'
|
||||
self._gitlab_path = None
|
||||
|
||||
# query author
|
||||
def _get_author(self, hash_):
|
||||
cmd = [self._executable, 'log', hash_, '-n', '1', '--format=format:%aN']
|
||||
result = self._run_command(cmd)
|
||||
if result['returncode']:
|
||||
raise RuntimeError('Could not fetch author:\n%s' % result['output'])
|
||||
return result['output']
|
||||
|
||||
def get_tags(self):
|
||||
# Get a decorated log, use the refnames to find the ancestor tags
|
||||
cmd_tag = [self._executable, 'log', '--simplify-by-decoration', '--decorate', '--pretty=oneline']
|
||||
result_tag = self._run_command(cmd_tag)
|
||||
if result_tag['returncode']:
|
||||
raise RuntimeError('Could not fetch tags:\n%s' % result_tag['output'])
|
||||
# Parse a comma-separated list of refname decorators out of the log
|
||||
decorations = ', '.join(re.findall(r'^[a-f0-9]+ \(([^)]*)\) .', result_tag['output'], re.MULTILINE)) + ','
|
||||
# Extract only refnames that are tags
|
||||
tag_names = re.findall('tag: ([^,]+)[,]', decorations)
|
||||
|
||||
tags = []
|
||||
for tag_name in tag_names:
|
||||
cmd = [self._executable, 'log', tag_name, '-n', '1', '--format=format:%ai']
|
||||
result = self._run_command(cmd)
|
||||
if result['returncode']:
|
||||
raise RuntimeError('Could not fetch timestamp:\n%s' % result['output'])
|
||||
tags.append(Tag(tag_name, result['output']))
|
||||
self._truncate_timestamps(tags)
|
||||
return tags
|
||||
|
||||
def get_latest_tag_name(self):
|
||||
cmd_describe = [self._executable, 'describe', '--abbrev=0', '--tags']
|
||||
result_describe = self._run_command(cmd_describe)
|
||||
if result_describe['returncode']:
|
||||
raise RuntimeError('Could not fetch latest tag:\n%s' % result_describe['output'])
|
||||
tag_name = result_describe['output']
|
||||
return tag_name
|
||||
|
||||
def get_log_entries(self, from_tag, to_tag, skip_merges=False, only_merges=False):
|
||||
# query all hashes in the range
|
||||
cmd = [self._executable, 'log']
|
||||
if from_tag or to_tag:
|
||||
cmd.append('%s%s' % ('%s..' % to_tag if to_tag else '', from_tag if from_tag else ''))
|
||||
cmd.append('--format=format:%H')
|
||||
if skip_merges and only_merges:
|
||||
raise RuntimeError('Both "skip_merges" and "only_merges" are set to True, which contradicts.')
|
||||
if skip_merges:
|
||||
cmd.append('--no-merges')
|
||||
if only_merges:
|
||||
cmd.append('--merges')
|
||||
result = self._run_command(cmd)
|
||||
if result['returncode']:
|
||||
raise RuntimeError('Could not fetch commit hashes:\n%s' % result['output'])
|
||||
|
||||
log_entries = []
|
||||
if result['output']:
|
||||
# query further information for each changeset
|
||||
hashes = result['output'].splitlines()
|
||||
for hash_ in hashes:
|
||||
# query commit message
|
||||
cmd = [self._executable, 'log', hash_, '-n', '1', '--format=format:%B']
|
||||
result = self._run_command(cmd)
|
||||
if result['returncode']:
|
||||
raise RuntimeError('Could not fetch commit message:\n%s' % result['output'])
|
||||
if result['output'] == from_tag:
|
||||
continue
|
||||
msg = result['output']
|
||||
# query affected paths
|
||||
cmd = [self._executable, 'show', '--first-parent', hash_, '--name-only', '--format=format:""']
|
||||
result = self._run_command(cmd)
|
||||
if result['returncode']:
|
||||
raise RuntimeError('Could not fetch affected paths:\n%s' % result['output'])
|
||||
affected_paths = result['output'].splitlines()
|
||||
log_entries.append(LogEntry(msg, affected_paths, self._get_author(hash_)))
|
||||
return log_entries
|
||||
|
||||
def replace_repository_references(self, line):
|
||||
if self._repo_hosting is None:
|
||||
self._repo_hosting = False
|
||||
try:
|
||||
self._determine_repo_hosting()
|
||||
except RuntimeError:
|
||||
pass
|
||||
if self._repo_hosting == 'github':
|
||||
line = self._replace_github_issue_references(line)
|
||||
elif self._repo_hosting == 'gitlab':
|
||||
line = self._replace_gitlab_issue_references(line)
|
||||
return line
|
||||
|
||||
def _determine_repo_hosting(self):
|
||||
cmd = [self._executable, 'config', '--get', 'remote.origin.url']
|
||||
result = self._run_command(cmd)
|
||||
if result['returncode']:
|
||||
raise RuntimeError('Could not fetch remote url:\n%s' % result['output'])
|
||||
|
||||
# detect github hosting
|
||||
prefixes = ['git@github.com:', 'https://github.com/', 'git://github.com/']
|
||||
for prefix in prefixes:
|
||||
if result['output'].startswith(prefix):
|
||||
self._repo_hosting = 'github'
|
||||
path = result['output'][len(prefix):]
|
||||
if path.endswith('.git'):
|
||||
path = path[:-4]
|
||||
self._github_path = path
|
||||
break
|
||||
|
||||
# detect gitlab hosting
|
||||
prefixes = ['git@gitlab.com:', 'https://gitlab.com/', 'git://gitlab.com/']
|
||||
for prefix in prefixes:
|
||||
if result['output'].startswith(prefix):
|
||||
self._repo_hosting = 'gitlab'
|
||||
path = result['output'][len(prefix):]
|
||||
if path.endswith('.git'):
|
||||
path = path[:-4]
|
||||
self._gitlab_path = path
|
||||
break
|
||||
|
||||
def _replace_github_issue_references(self, line):
|
||||
valid_name = '[\\w._-]+'
|
||||
issue_pattern = '#(\\d+)'
|
||||
|
||||
def replace_issue_number(match):
|
||||
issue_url = self._github_base_url
|
||||
if match.group(1):
|
||||
path = match.group(1)
|
||||
issue_url += path
|
||||
else:
|
||||
path = ''
|
||||
issue_url += self._github_path
|
||||
issue_number = match.group(2)
|
||||
issue_url += '/issues/' + issue_number
|
||||
return '`%s#%s <%s>`_' % (path, issue_number, issue_url)
|
||||
line = re.sub(('(%s/%s)?' % (valid_name, valid_name)) + issue_pattern, replace_issue_number, line)
|
||||
return line
|
||||
|
||||
def _replace_gitlab_issue_references(self, line):
|
||||
valid_name = '[\\w._-]+'
|
||||
issue_pattern = '#(\\d+)'
|
||||
merge_request_pattern = '!(\\d+)'
|
||||
|
||||
def replace_issue_number(match):
|
||||
issue_url = self._gitlab_base_url
|
||||
if match.group(1):
|
||||
path = match.group(1)
|
||||
issue_url += path
|
||||
else:
|
||||
path = ''
|
||||
issue_url += self._gitlab_path
|
||||
issue_number = match.group(3)
|
||||
issue_url += '/-/issues/' + issue_number
|
||||
return '`%s#%s <%s>`_' % (path, issue_number, issue_url)
|
||||
line = re.sub(('(%s(/%s)+)?' % (valid_name, valid_name)) + issue_pattern, replace_issue_number, line)
|
||||
|
||||
def replace_merge_request_number(match):
|
||||
merge_request_url = self._gitlab_base_url
|
||||
if match.group(1):
|
||||
path = match.group(1)
|
||||
merge_request_url += path
|
||||
else:
|
||||
path = ''
|
||||
merge_request_url += self._gitlab_path
|
||||
merge_request_number = match.group(3)
|
||||
merge_request_url += '/-/merge_requests/' + merge_request_number
|
||||
return '`%s!%s <%s>`_' % (path, merge_request_number, merge_request_url)
|
||||
line = re.sub(('(%s(/%s)+)?' % (valid_name, valid_name)) + merge_request_pattern, replace_merge_request_number, line)
|
||||
return line
|
||||
|
||||
|
||||
class HgClient(VcsClientBase):
|
||||
|
||||
type = 'hg' # noqa: A003
|
||||
|
||||
def __init__(self, path):
|
||||
super(HgClient, self).__init__(path)
|
||||
self._executable = which('hg')
|
||||
|
||||
# query author
|
||||
def _get_author(self, hash_):
|
||||
cmd = [self._executable, 'log', '-r', hash_, '--template', '{author}']
|
||||
result = self._run_command(cmd)
|
||||
if result['returncode']:
|
||||
raise RuntimeError('Could not fetch author:\n%s' % result['output'])
|
||||
return result['output']
|
||||
|
||||
def get_tags(self):
|
||||
cmd_tag = [self._executable, 'tags', '-q']
|
||||
result_tag = self._run_command(cmd_tag)
|
||||
if result_tag['returncode']:
|
||||
raise RuntimeError('Could not fetch tags:\n%s' % result_tag['output'])
|
||||
tag_names = result_tag['output'].splitlines()
|
||||
|
||||
tags = []
|
||||
for tag_name in tag_names:
|
||||
cmd = [self._executable, 'log', '-r', tag_name, '--template', '{date|isodatesec}']
|
||||
result = self._run_command(cmd)
|
||||
if result['returncode']:
|
||||
raise RuntimeError('Could not fetch timestamp:\n%s' % result['output'])
|
||||
tags.append(Tag(tag_name, result['output']))
|
||||
self._truncate_timestamps(tags)
|
||||
return tags
|
||||
|
||||
def get_latest_tag_name(self):
|
||||
cmd_log = [self._executable, 'log', '--rev', '.', '--template', '{latesttag}']
|
||||
result_log = self._run_command(cmd_log)
|
||||
if result_log['returncode']:
|
||||
raise RuntimeError('Could not fetch latest tag:\n%s' % result_log['output'])
|
||||
tag_name = result_log['output']
|
||||
if tag_name == 'null':
|
||||
raise RuntimeError('Could not find latest tagn')
|
||||
return tag_name
|
||||
|
||||
def get_log_entries(self, from_tag, to_tag, skip_merges=False, only_merges=False):
|
||||
# query all hashes in the range
|
||||
# ascending chronological order since than it is easier to handle empty tag names
|
||||
revrange = '%s:%s' % ((to_tag if to_tag else ''), (from_tag if from_tag else 'tip'))
|
||||
if to_tag:
|
||||
revrange += '-%s' % to_tag
|
||||
if from_tag:
|
||||
revrange += '-%s' % from_tag
|
||||
cmd = [self._executable, 'log', '-r', revrange, '--template', '{rev}\n']
|
||||
result = self._run_command(cmd)
|
||||
if result['returncode']:
|
||||
raise RuntimeError('Could not fetch commit hashes:\n%s' % result['output'])
|
||||
|
||||
tmp_base = tempfile.mkdtemp('-hg-style')
|
||||
try:
|
||||
style_file = os.path.join(tmp_base, 'hg-changeset-files-per-line.style')
|
||||
with open(style_file, 'w') as f:
|
||||
f.write("changeset = '{files}'\n")
|
||||
f.write("file = '{file}\\n'\n")
|
||||
|
||||
log_entries = []
|
||||
if result['output']:
|
||||
# query further information for each changeset
|
||||
revs = reversed(result['output'].splitlines())
|
||||
for rev in revs:
|
||||
# query commit message
|
||||
cmd = [self._executable, 'log', '-r', rev, '-l', '1', '--template', '{desc}']
|
||||
result = self._run_command(cmd)
|
||||
if result['returncode']:
|
||||
raise RuntimeError('Could not fetch commit message:\n%s' % result['output'])
|
||||
if result['output'] == from_tag:
|
||||
continue
|
||||
msg = result['output']
|
||||
# query affected paths
|
||||
cmd = [self._executable, 'log', '-r', rev, '-l', '1', '--style', style_file]
|
||||
result = self._run_command(cmd)
|
||||
if result['returncode']:
|
||||
raise RuntimeError('Could not fetch affected paths:\n%s' % result['output'])
|
||||
affected_paths = result['output'].splitlines()
|
||||
log_entries.append(LogEntry(msg, affected_paths, self._get_author(rev)))
|
||||
finally:
|
||||
shutil.rmtree(tmp_base)
|
||||
return log_entries
|
||||
|
||||
|
||||
def get_vcs_client(base_path):
|
||||
vcs_clients = []
|
||||
vcs_clients.append(GitClient)
|
||||
vcs_clients.append(HgClient)
|
||||
client_types = [c.type for c in vcs_clients]
|
||||
if len(client_types) != len(set(client_types)):
|
||||
raise RuntimeError('Multiple vcs clients share the same type: %s' % ', '.join(sorted(client_types)))
|
||||
|
||||
for vcs_client in vcs_clients:
|
||||
if os.path.exists(os.path.join(base_path, '.%s' % vcs_client.type)):
|
||||
return vcs_client(base_path)
|
||||
raise RuntimeError('Could not detect repository type - currently supports: %s' % ', '.join([c.type for c in vcs_clients]))
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,74 @@
|
|||
"""This script creates the skeletton of a catkin package."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
from catkin_pkg.package_templates import create_package_files, PackageTemplate
|
||||
|
||||
|
||||
def main(argv=sys.argv[1:], parent_path=os.getcwd()):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Creates a new catkin package')
|
||||
parser.add_argument('name',
|
||||
nargs=1,
|
||||
help='The name for the package')
|
||||
parser.add_argument('--meta',
|
||||
action='store_true',
|
||||
help='Creates meta-package files')
|
||||
parser.add_argument('dependencies',
|
||||
nargs='*',
|
||||
help='Catkin package Dependencies')
|
||||
parser.add_argument('-s', '--sys-deps',
|
||||
nargs='*',
|
||||
help='System Dependencies')
|
||||
parser.add_argument('-b', '--boost-comps',
|
||||
nargs='*',
|
||||
help='Boost Components')
|
||||
parser.add_argument('-V', '--pkg_version',
|
||||
action='store',
|
||||
help='Initial Package version')
|
||||
parser.add_argument('-D', '--description',
|
||||
action='store',
|
||||
help='Description')
|
||||
parser.add_argument('-l', '--license',
|
||||
action='append',
|
||||
help='Name for License, (e.g. BSD, MIT, GPLv3...)')
|
||||
parser.add_argument('-a', '--author',
|
||||
action='append',
|
||||
help='A single author, may be used multiple times')
|
||||
parser.add_argument('-m', '--maintainer',
|
||||
action='append',
|
||||
help='A single maintainer, may be used multiple times')
|
||||
rosdistro_name = os.environ['ROS_DISTRO'] if 'ROS_DISTRO' in os.environ else None
|
||||
parser.add_argument('--rosdistro', required=rosdistro_name is None, default=rosdistro_name, help='The ROS distro (default: environment variable ROS_DISTRO if defined)')
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
try:
|
||||
package_name = args.name[0]
|
||||
target_path = os.path.join(parent_path, package_name)
|
||||
package_template = PackageTemplate._create_package_template(
|
||||
package_name=package_name,
|
||||
description=args.description,
|
||||
licenses=args.license or [],
|
||||
maintainer_names=args.maintainer,
|
||||
author_names=args.author,
|
||||
version=args.pkg_version,
|
||||
catkin_deps=args.dependencies,
|
||||
system_deps=args.sys_deps,
|
||||
boost_comps=args.boost_comps)
|
||||
create_package_files(target_path=target_path,
|
||||
package_template=package_template,
|
||||
rosdistro=args.rosdistro,
|
||||
newfiles={},
|
||||
meta=args.meta)
|
||||
print('Successfully created files in %s. Please adjust the values in package.xml.' % target_path)
|
||||
except ValueError as vae:
|
||||
parser.error(str(vae))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
"""This script finds a catkin packages."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
from catkin_pkg.packages import find_packages
|
||||
|
||||
|
||||
def main(argv=sys.argv[1:]):
|
||||
parser = argparse.ArgumentParser(description='Find a catkin package')
|
||||
parser.add_argument('pkg', help='The name of the package')
|
||||
parser.add_argument('base_path', nargs='?', default=os.curdir, help='The base path to crawl for packages')
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
try:
|
||||
packages = find_packages(args.base_path)
|
||||
catkin_pkg = [path for path, p in packages.items() if p.name == args.pkg]
|
||||
if catkin_pkg:
|
||||
print(catkin_pkg[0])
|
||||
else:
|
||||
print("Could not find package '%s'." % args.pkg, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
except RuntimeError as e:
|
||||
print('ERROR: ' + str(e), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
"""This script generates REP-0132 CHANGELOG.rst files for git or hg repositories."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from catkin_pkg.changelog import CHANGELOG_FILENAME
|
||||
from catkin_pkg.changelog_generator import generate_changelog_file, generate_changelogs, get_all_changes, get_forthcoming_changes, update_changelogs
|
||||
from catkin_pkg.changelog_generator_vcs import get_vcs_client
|
||||
from catkin_pkg.packages import find_packages
|
||||
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
raw_input = input # noqa: A001
|
||||
|
||||
|
||||
def prompt_continue(msg, default):
|
||||
"""Prompt the user for continuation."""
|
||||
if default:
|
||||
msg += ' [Y/n]?'
|
||||
else:
|
||||
msg += ' [y/N]?'
|
||||
|
||||
while True:
|
||||
response = raw_input(msg)
|
||||
if not response:
|
||||
response = 'y' if default else 'n'
|
||||
else:
|
||||
response = response.lower()
|
||||
|
||||
if response in ['y', 'n']:
|
||||
return response == 'y'
|
||||
|
||||
print("Response '%s' was not recognized, please use one of the following options: y, Y, n, N" % response, file=sys.stderr)
|
||||
|
||||
|
||||
def main(sysargs=None):
|
||||
parser = argparse.ArgumentParser(description='Generate a REP-0132 %s' % CHANGELOG_FILENAME)
|
||||
group_merge = parser.add_mutually_exclusive_group()
|
||||
parser.add_argument(
|
||||
'-a', '--all', action='store_true', default=False,
|
||||
help='Generate changelog for all versions instead of only the forthcoming one (only supported when no changelog file exists yet)')
|
||||
group_merge.add_argument(
|
||||
'--only-merges', action='store_true', default=False,
|
||||
help='Only add merge commits to the changelog')
|
||||
parser.add_argument(
|
||||
'--print-root', action='store_true', default=False,
|
||||
help='Output changelog content to the console as if there would be only one package in the root of the repository')
|
||||
parser.add_argument(
|
||||
'--skip-contributors', action='store_true', default=False,
|
||||
help='Skip adding the list of contributors to the changelog')
|
||||
group_merge.add_argument(
|
||||
'--skip-merges', action='store_true', default=False,
|
||||
help='Skip adding merge commits to the changelog')
|
||||
parser.add_argument(
|
||||
'-y', '--non-interactive', action='store_true', default=False,
|
||||
help="Run without user interaction, confirming all questions with 'yes'")
|
||||
args = parser.parse_args(sysargs)
|
||||
|
||||
base_path = '.'
|
||||
logging.basicConfig(format='%(message)s', level=logging.DEBUG)
|
||||
|
||||
vcs_client = get_vcs_client(base_path)
|
||||
|
||||
if args.print_root:
|
||||
# printing status messages to stderr to allow piping the changelog to a file
|
||||
if args.all:
|
||||
print('Querying all tags and commit information...', file=sys.stderr)
|
||||
tag2log_entries = get_all_changes(vcs_client, skip_merges=args.skip_merges, only_merges=args.only_merges)
|
||||
print('Generating changelog output with all versions...', file=sys.stderr)
|
||||
else:
|
||||
print('Querying commit information since latest tag...', file=sys.stderr)
|
||||
tag2log_entries = get_forthcoming_changes(vcs_client, skip_merges=args.skip_merges, only_merges=args.only_merges)
|
||||
print('Generating changelog files with forthcoming version...', file=sys.stderr)
|
||||
print('', file=sys.stderr)
|
||||
data = generate_changelog_file('repository-level', tag2log_entries, vcs_client=vcs_client)
|
||||
print(data)
|
||||
return 0
|
||||
|
||||
# find packages
|
||||
packages = find_packages(base_path)
|
||||
if not packages:
|
||||
raise RuntimeError('No packages found')
|
||||
print('Found packages: %s' % ', '.join(sorted(p.name for p in packages.values())))
|
||||
|
||||
# check for missing changelogs
|
||||
missing_changelogs = []
|
||||
for pkg_path, package in packages.items():
|
||||
changelog_path = os.path.join(base_path, pkg_path, CHANGELOG_FILENAME)
|
||||
if not os.path.exists(changelog_path):
|
||||
missing_changelogs.append(package.name)
|
||||
|
||||
if args.all and not missing_changelogs:
|
||||
raise RuntimeError('All packages already have a changelog. Either remove (some of) them before using --all or invoke the script without --all.')
|
||||
|
||||
if args.all and len(missing_changelogs) != len(packages):
|
||||
ignored = set([p.name for p in packages.values()]) - set(missing_changelogs)
|
||||
print('The following packages already have a changelog file and will be ignored: %s' % ', '.join(sorted(ignored)), file=sys.stderr)
|
||||
|
||||
# prompt to switch to --all
|
||||
if not args.all and missing_changelogs:
|
||||
print('Some of the packages have no changelog file: %s' % ', '.join(sorted(missing_changelogs)))
|
||||
print('You might consider to use --all to generate the changelogs for all versions (not only for the forthcoming version).')
|
||||
if not args.non_interactive and not prompt_continue('Continue without --all option', default=False):
|
||||
raise RuntimeError('Skipping generation, rerun the script with --all.')
|
||||
|
||||
if args.all:
|
||||
print('Querying all tags and commit information...')
|
||||
tag2log_entries = get_all_changes(vcs_client, skip_merges=args.skip_merges, only_merges=args.only_merges)
|
||||
print('Generating changelog files with all versions...')
|
||||
generate_changelogs(base_path, packages, tag2log_entries, logger=logging, vcs_client=vcs_client, skip_contributors=args.skip_contributors)
|
||||
else:
|
||||
print('Querying commit information since latest tag...')
|
||||
tag2log_entries = get_forthcoming_changes(vcs_client, skip_merges=args.skip_merges, only_merges=args.only_merges)
|
||||
# separate packages with/without a changelog file
|
||||
packages_without = {pkg_path: package for pkg_path, package in packages.items() if package.name in missing_changelogs}
|
||||
if packages_without:
|
||||
print('Generating changelog files with forthcoming version...')
|
||||
generate_changelogs(base_path, packages_without, tag2log_entries, logger=logging, vcs_client=vcs_client, skip_contributors=args.skip_contributors)
|
||||
packages_with = {pkg_path: package for pkg_path, package in packages.items() if package.name not in missing_changelogs}
|
||||
if packages_with:
|
||||
print('Updating forthcoming section of changelog files...')
|
||||
update_changelogs(base_path, packages_with, tag2log_entries, logger=logging, vcs_client=vcs_client, skip_contributors=args.skip_contributors)
|
||||
print('Done.')
|
||||
print('Please review the extracted commit messages and consolidate the changelog entries before committing the files!')
|
||||
|
||||
|
||||
def main_catching_runtime_error(*args, **kwargs):
|
||||
try:
|
||||
main(*args, **kwargs)
|
||||
except RuntimeError as e:
|
||||
print('ERROR: ' + str(e), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main_catching_runtime_error())
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
from catkin_pkg.package_version import bump_version
|
||||
from catkin_pkg.package_version import update_versions
|
||||
from catkin_pkg.packages import find_packages, verify_equal_package_versions
|
||||
|
||||
# find the import relatively if available to work before installing catkin or overlaying installed version
|
||||
if os.path.exists(os.path.join(os.path.dirname(__file__), '..', 'python', 'catkin', '__init__.py')):
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'python'))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Show or bump the version number in package.xml files.')
|
||||
parser.add_argument('path', nargs='?', default='.', help='The path to a parent folder which contains package.xml files (default: .)')
|
||||
parser.add_argument('--bump', choices=('major', 'minor', 'patch'), help='Which part of the version number to bump?')
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
packages = find_packages(args.path)
|
||||
if not packages:
|
||||
print('No packages found', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
version = verify_equal_package_versions(packages.values())
|
||||
|
||||
# only print the version number
|
||||
if args.bump is None:
|
||||
print(version)
|
||||
|
||||
else:
|
||||
# bump the version number
|
||||
new_version = bump_version(version, args.bump)
|
||||
update_versions(packages, new_version)
|
||||
print('%s -> %s' % (version, new_version))
|
||||
except Exception as e: # noqa: B902
|
||||
sys.exit(str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,455 @@
|
|||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from catkin_pkg import metapackage
|
||||
from catkin_pkg.changelog import CHANGELOG_FILENAME, get_changelog_from_path
|
||||
from catkin_pkg.package import InvalidPackage, PACKAGE_MANIFEST_FILENAME
|
||||
from catkin_pkg.package_version import bump_version
|
||||
from catkin_pkg.package_version import get_forthcoming_label, update_changelog_sections, update_versions
|
||||
from catkin_pkg.packages import find_packages, verify_equal_package_versions
|
||||
from catkin_pkg.terminal_color import disable_ANSI_colors, fmt
|
||||
from catkin_pkg.workspace_vcs import get_repository_type, vcs_remotes
|
||||
|
||||
try:
|
||||
from shutil import which
|
||||
except ImportError:
|
||||
# fallback for Python < 3.3
|
||||
def which(exe):
|
||||
for path in os.getenv('PATH').split(os.path.pathsep):
|
||||
file_path = os.path.join(path, exe)
|
||||
if os.path.isfile(file_path):
|
||||
return file_path
|
||||
return None
|
||||
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
raw_input = input # noqa: A001
|
||||
|
||||
|
||||
def has_changes(base_path, path, vcs_type):
|
||||
cmd = [_find_executable(vcs_type), 'diff', path]
|
||||
try:
|
||||
output = subprocess.check_output(cmd, cwd=base_path)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(fmt("@{rf}Failed to check if '@{boldon}%s@{boldoff}' has modifications: %s" % (path, str(e))))
|
||||
return output.decode('utf-8').rstrip() != ''
|
||||
|
||||
|
||||
def prompt_continue(msg, default):
|
||||
"""Prompt the user for continuation."""
|
||||
if default:
|
||||
msg += fmt(' @{yf}[Y/n]@{reset}?')
|
||||
else:
|
||||
msg += fmt(' @{yf}[y/N]@{reset}?')
|
||||
|
||||
while True:
|
||||
_flush_stdin()
|
||||
try:
|
||||
response = raw_input(msg)
|
||||
except EOFError:
|
||||
response = ''
|
||||
if not response:
|
||||
response = 'y' if default else 'n'
|
||||
else:
|
||||
response = response.lower()
|
||||
|
||||
if response in ['y', 'n']:
|
||||
return response == 'y'
|
||||
|
||||
print(
|
||||
fmt(
|
||||
"@{yf}Response '@{boldon}%s@{boldoff}' was not recognized, please use one of the following options: %s" %
|
||||
(response, ', '.join([('@{boldon}%s@{boldoff}' % x) for x in ['y', 'Y', 'n', 'N']]))
|
||||
), file=sys.stderr)
|
||||
|
||||
|
||||
def _flush_stdin():
|
||||
try:
|
||||
from termios import tcflush, TCIFLUSH
|
||||
tcflush(sys.stdin, TCIFLUSH)
|
||||
except ImportError:
|
||||
# fallback if not supported on some platforms
|
||||
pass
|
||||
|
||||
|
||||
def get_git_branch(base_path):
|
||||
cmd_branch = [_find_executable('git'), 'rev-parse', '--abbrev-ref', 'HEAD']
|
||||
try:
|
||||
branch = subprocess.check_output(cmd_branch, cwd=base_path)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(fmt('@{rf}Could not determine git branch: %s' % str(e)))
|
||||
return branch.decode('utf-8').rstrip()
|
||||
|
||||
|
||||
def get_git_remote(base_path):
|
||||
branch = get_git_branch(base_path)
|
||||
|
||||
cmd_remote = [_find_executable('git'), 'config', '--get', 'branch.%s.remote' % branch]
|
||||
try:
|
||||
remote = subprocess.check_output(cmd_remote, cwd=base_path)
|
||||
except subprocess.CalledProcessError as e:
|
||||
msg = 'Could not determine git remote: %s' % str(e)
|
||||
msg += "\n\nMay be the branch '%s' is not tracking a remote branch?" % branch
|
||||
raise RuntimeError(fmt('@{rf}%s' % msg))
|
||||
return remote.decode('utf-8').rstrip()
|
||||
|
||||
|
||||
def try_repo_push(base_path, vcs_type):
|
||||
if vcs_type in ['git']:
|
||||
print('Trying to push to remote repository (dry run)...')
|
||||
cmd = [_find_executable(vcs_type), 'push']
|
||||
if vcs_type == 'git':
|
||||
cmd.extend(['-n'] + [get_git_remote(base_path), get_git_branch(base_path)])
|
||||
try:
|
||||
subprocess.check_call(cmd, cwd=base_path)
|
||||
except (subprocess.CalledProcessError, RuntimeError) as e:
|
||||
raise RuntimeError(fmt('@{rf}Failed to dry push to repository: %s' % str(e)))
|
||||
|
||||
|
||||
def check_clean_working_copy(base_path, vcs_type):
|
||||
if vcs_type in ['bzr', 'hg', 'svn']:
|
||||
cmd = [_find_executable(vcs_type), 'status']
|
||||
elif vcs_type in ['git']:
|
||||
cmd = [_find_executable(vcs_type), 'status', '-s', '-u']
|
||||
else:
|
||||
assert False, 'Unknown vcs type: %s' % vcs_type
|
||||
try:
|
||||
output = subprocess.check_output(cmd, cwd=base_path)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(fmt('@{rf}Failed to check working copy state: %s' % str(e)))
|
||||
output = output.decode('utf-8').rstrip()
|
||||
if output != '':
|
||||
print(output)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def commit_files(base_path, vcs_type, packages, packages_with_changelogs, message, dry_run=False):
|
||||
cmd = [_find_executable(vcs_type), 'commit', '-m', message]
|
||||
cmd += [os.path.join(p, PACKAGE_MANIFEST_FILENAME) for p in packages.keys()]
|
||||
cmd += [s for s in [os.path.join(p, 'setup.py') for p in packages.keys()] if os.path.exists(s)]
|
||||
cmd += [path for path, _, _ in packages_with_changelogs.values()]
|
||||
if not dry_run:
|
||||
try:
|
||||
subprocess.check_call(cmd, cwd=base_path)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(fmt('@{rf}Failed to commit package.xml files: %s' % str(e)))
|
||||
return cmd
|
||||
|
||||
|
||||
def tag_repository(base_path, vcs_type, tag_name, has_tag_prefix, dry_run=False):
|
||||
if vcs_type in ['bzr', 'git', 'hg']:
|
||||
cmd = [_find_executable(vcs_type), 'tag', tag_name]
|
||||
elif vcs_type == 'svn':
|
||||
svn_url = vcs_remotes(base_path, 'svn')[5:]
|
||||
if os.path.basename(svn_url) == 'trunk':
|
||||
# tag "trunk"
|
||||
base_url = os.path.dirname(svn_url)
|
||||
elif os.path.basename(os.path.dirname(svn_url)) == 'branches':
|
||||
# tag a direct subfolder of "branches"
|
||||
base_url = os.path.dirname(os.path.dirname(svn_url))
|
||||
elif svn_url.rfind('/trunk/') != -1:
|
||||
# tag any subfolder of trunk but require a tag prefix
|
||||
if not has_tag_prefix:
|
||||
raise RuntimeError(fmt('@{rf}When tagging a subfolder you must use --tag-prefix to make your tag name unique'))
|
||||
base_url = svn_url[:svn_url.rfind('/trunk/')]
|
||||
elif svn_url.rfind('/branches/') != -1:
|
||||
# tag any subfolder of trunk but require a tag prefix
|
||||
if not has_tag_prefix:
|
||||
raise RuntimeError(fmt('@{rf}When tagging a subfolder you must use --tag-prefix to make your tag name unique'))
|
||||
base_url = svn_url[:svn_url.rfind('/branches/')]
|
||||
else:
|
||||
raise RuntimeError(fmt("@{rf}Could not determine base URL of SVN repository '%s'" % svn_url))
|
||||
tag_url = '%s/tags/%s' % (base_url, tag_name)
|
||||
cmd = ['svn', 'cp', '-m', '"tagging %s"' % tag_name, svn_url, tag_url]
|
||||
else:
|
||||
assert False, 'Unknown vcs type: %s' % vcs_type
|
||||
if not dry_run:
|
||||
try:
|
||||
subprocess.check_call(cmd, cwd=base_path)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(fmt('@{rf}Failed to tag repository: %s' % str(e)))
|
||||
return cmd
|
||||
|
||||
|
||||
def push_changes(base_path, vcs_type, tag_name, dry_run=False):
|
||||
commands = []
|
||||
|
||||
# push changes to the repository
|
||||
cmd = [_find_executable(vcs_type), 'push']
|
||||
if vcs_type == 'git':
|
||||
cmd.extend([get_git_remote(base_path), get_git_branch(base_path)])
|
||||
commands.append(cmd)
|
||||
if not dry_run:
|
||||
try:
|
||||
subprocess.check_call(cmd, cwd=base_path)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(fmt('@{rf}Failed to push changes to the repository: %s\n\nYou need to manually push the changes/tag to the repository.' % str(e)))
|
||||
|
||||
# push tags to the repository
|
||||
if vcs_type in ['git']:
|
||||
cmd = [_find_executable(vcs_type), 'push', get_git_remote(base_path), tag_name]
|
||||
commands.append(cmd)
|
||||
if not dry_run:
|
||||
try:
|
||||
subprocess.check_call(cmd, cwd=base_path)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(fmt('@{rf}Failed to push tag to the repository: %s\n\nYou need to manually push the new tag to the repository.' % str(e)))
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def _find_executable(vcs_type):
|
||||
file_path = which(vcs_type)
|
||||
if file_path is None:
|
||||
raise RuntimeError(fmt('@{rf}Could not find vcs binary: %s' % vcs_type))
|
||||
return file_path
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
_main()
|
||||
except RuntimeError as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Runs the commands to bump the version number, commit the modified %s files and create a tag in the repository.' % PACKAGE_MANIFEST_FILENAME)
|
||||
parser.add_argument('--bump', choices=('major', 'minor', 'patch'), default='patch', help='Which part of the version number to bump? (default: %(default)s)')
|
||||
parser.add_argument('--version', help='Specify a specific version to use')
|
||||
parser.add_argument('--no-color', action='store_true', default=False, help='Disables colored output')
|
||||
parser.add_argument('--no-push', action='store_true', default=False, help='Disables pushing to remote repository')
|
||||
parser.add_argument('-t', '--tag-prefix', default='', help='Add this prefix to the created release tag')
|
||||
parser.add_argument('-y', '--non-interactive', action='store_true', default=False, help="Run without user interaction, confirming all questions with 'yes'")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.version and not re.match(r'^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$', args.version):
|
||||
parser.error('The passed version must follow the conventions (positive integers x.y.z with no leading zeros)')
|
||||
|
||||
if args.tag_prefix and ' ' in args.tag_prefix:
|
||||
parser.error('The tag prefix must not contain spaces')
|
||||
|
||||
# force --no-color if stdout is non-interactive
|
||||
if not sys.stdout.isatty():
|
||||
args.no_color = True
|
||||
# disable colors if asked
|
||||
if args.no_color:
|
||||
disable_ANSI_colors()
|
||||
|
||||
base_path = '.'
|
||||
|
||||
print(fmt('@{gf}Prepare the source repository for a release.'))
|
||||
|
||||
# determine repository type
|
||||
vcs_type = get_repository_type(base_path)
|
||||
if vcs_type is None:
|
||||
raise RuntimeError(fmt("@{rf}Could not determine repository type of @{boldon}'%s'@{boldoff}" % base_path))
|
||||
print(fmt('Repository type: @{boldon}%s@{boldoff}' % vcs_type))
|
||||
|
||||
# find packages
|
||||
try:
|
||||
packages = find_packages(base_path)
|
||||
except InvalidPackage as e:
|
||||
raise RuntimeError(fmt("@{rf}Invalid package at path @{boldon}'%s'@{boldoff}:\n %s" % (os.path.abspath(base_path), str(e))))
|
||||
if not packages:
|
||||
raise RuntimeError(fmt('@{rf}No packages found'))
|
||||
print('Found packages: %s' % ', '.join([fmt('@{bf}@{boldon}%s@{boldoff}@{reset}' % p.name) for p in packages.values()]))
|
||||
|
||||
# complain about packages with unsupported build_type as they might require additional steps before being released
|
||||
# complain about packages with upper case character since they won't be releasable with bloom
|
||||
unsupported_pkg_names = []
|
||||
invalid_pkg_names = []
|
||||
valid_build_types = ['catkin', 'ament_cmake', 'ament_python']
|
||||
for package in packages.values():
|
||||
build_types = package.get_unconditional_build_types()
|
||||
if any(build_type not in valid_build_types for build_type in build_types):
|
||||
unsupported_pkg_names.append(package.name)
|
||||
if package.name != package.name.lower():
|
||||
invalid_pkg_names.append(package.name)
|
||||
if unsupported_pkg_names:
|
||||
print(
|
||||
fmt(
|
||||
"@{yf}Warning: the following package are not of build_type %s and may require manual steps to release': %s" %
|
||||
(str(valid_build_types), ', '.join([('@{boldon}%s@{boldoff}' % p) for p in sorted(unsupported_pkg_names)]))
|
||||
), file=sys.stderr)
|
||||
if not args.non_interactive and not prompt_continue('Continue anyway', default=False):
|
||||
raise RuntimeError(fmt('@{rf}Aborted release, verify that unsupported packages are ready to be released or release manually.'))
|
||||
if invalid_pkg_names:
|
||||
print(
|
||||
fmt(
|
||||
"@{yf}Warning: the following package names contain upper case characters which violate both ROS and Debian naming conventions': %s" %
|
||||
', '.join([('@{boldon}%s@{boldoff}' % p) for p in sorted(invalid_pkg_names)])
|
||||
), file=sys.stderr)
|
||||
if not args.non_interactive and not prompt_continue('Continue anyway', default=False):
|
||||
raise RuntimeError(fmt('@{rf}Aborted release, fix the names of the packages.'))
|
||||
|
||||
local_modifications = []
|
||||
for pkg_path, package in packages.items():
|
||||
# verify that the package.xml files don't have modifications pending
|
||||
package_xml_path = os.path.join(pkg_path, PACKAGE_MANIFEST_FILENAME)
|
||||
if has_changes(base_path, package_xml_path, vcs_type):
|
||||
local_modifications.append(package_xml_path)
|
||||
# verify that metapackages are valid
|
||||
if package.is_metapackage():
|
||||
try:
|
||||
metapackage.validate_metapackage(pkg_path, package)
|
||||
except metapackage.InvalidMetapackage as e:
|
||||
raise RuntimeError(fmt(
|
||||
"@{rf}Invalid metapackage at path '@{boldon}%s@{boldoff}':\n %s\n\nSee requirements for metapackages: %s" %
|
||||
(os.path.abspath(pkg_path), str(e), metapackage.DEFINITION_URL)))
|
||||
# verify that the setup.py files don't have modifications pending
|
||||
setup_py_path = os.path.join(pkg_path, 'setup.py')
|
||||
if os.path.exists(setup_py_path) and has_changes(base_path, setup_py_path, vcs_type):
|
||||
local_modifications.append(setup_py_path)
|
||||
|
||||
# fetch current version and verify that all packages have same version number
|
||||
old_version = verify_equal_package_versions(packages.values())
|
||||
if args.version:
|
||||
new_version = args.version
|
||||
else:
|
||||
new_version = bump_version(old_version, args.bump)
|
||||
tag_name = args.tag_prefix + new_version
|
||||
|
||||
if (
|
||||
not args.non_interactive and
|
||||
not prompt_continue(
|
||||
fmt(
|
||||
"Prepare release of version '@{bf}@{boldon}%s@{boldoff}@{reset}'%s" %
|
||||
(new_version, " (tagged as '@{bf}@{boldon}%s@{boldoff}@{reset}')" % tag_name if args.tag_prefix else '')
|
||||
), default=True)
|
||||
):
|
||||
raise RuntimeError(fmt("@{rf}Aborted release, use option '--bump' to release a different version and/or '--tag-prefix' to add a prefix to the tag name."))
|
||||
|
||||
# check for changelog entries
|
||||
missing_changelogs = []
|
||||
missing_changelogs_but_forthcoming = {}
|
||||
for pkg_path, package in packages.items():
|
||||
changelog_path = os.path.join(pkg_path, CHANGELOG_FILENAME)
|
||||
if not os.path.exists(changelog_path):
|
||||
missing_changelogs.append(package.name)
|
||||
continue
|
||||
# verify that the changelog files don't have modifications pending
|
||||
if has_changes(base_path, changelog_path, vcs_type):
|
||||
local_modifications.append(changelog_path)
|
||||
changelog = get_changelog_from_path(changelog_path, package.name)
|
||||
try:
|
||||
changelog.get_content_of_version(new_version)
|
||||
except KeyError:
|
||||
# check that forthcoming section exists
|
||||
forthcoming_label = get_forthcoming_label(changelog.rst)
|
||||
if forthcoming_label:
|
||||
missing_changelogs_but_forthcoming[package.name] = (changelog_path, changelog, forthcoming_label)
|
||||
else:
|
||||
missing_changelogs.append(package.name)
|
||||
|
||||
if local_modifications:
|
||||
raise RuntimeError(fmt('@{rf}The following files have modifications, please commit/revert them before:' + ''.join([('\n- @{boldon}%s@{boldoff}' % path) for path in local_modifications])))
|
||||
|
||||
if missing_changelogs:
|
||||
print(
|
||||
fmt(
|
||||
"@{yf}Warning: the following packages do not have a changelog file or entry for version '@{boldon}%s@{boldoff}': %s" %
|
||||
(new_version, ', '.join([('@{boldon}%s@{boldoff}' % p) for p in sorted(missing_changelogs)]))
|
||||
), file=sys.stderr)
|
||||
if not args.non_interactive and not prompt_continue('Continue without changelogs', default=False):
|
||||
raise RuntimeError(fmt("@{rf}Aborted release, populate the changelog with '@{boldon}catkin_generate_changelog@{boldoff}' and review / clean up the content."))
|
||||
|
||||
# verify that repository is pushable (if the vcs supports dry run of push)
|
||||
if not args.no_push:
|
||||
try_repo_push(base_path, vcs_type)
|
||||
|
||||
# check for staged changes and modified and untracked files
|
||||
print(fmt('@{gf}Checking if working copy is clean (no staged changes, no modified files, no untracked files)...'))
|
||||
is_clean = check_clean_working_copy(base_path, vcs_type)
|
||||
if not is_clean:
|
||||
print(fmt('@{yf}Warning: the working copy contains other changes. Consider reverting/committing/stashing them before preparing a release.'), file=sys.stderr)
|
||||
if not args.non_interactive and not prompt_continue('Continue anyway', default=False):
|
||||
raise RuntimeError(fmt('@{rf}Aborted release, clean the working copy before trying again.'))
|
||||
|
||||
# for svn verify that we know how to tag that repository
|
||||
if vcs_type in ['svn']:
|
||||
tag_svn_cmd = tag_repository(base_path, vcs_type, tag_name, args.tag_prefix != '', dry_run=True)
|
||||
|
||||
# tag forthcoming changelog sections
|
||||
update_changelog_sections(missing_changelogs_but_forthcoming, new_version)
|
||||
print(fmt(
|
||||
"@{gf}Rename the forthcoming section@{reset} of the following packages to version '@{bf}@{boldon}%s@{boldoff}@{reset}': %s" %
|
||||
(new_version, ', '.join([('@{boldon}%s@{boldoff}' % p) for p in sorted(missing_changelogs_but_forthcoming.keys())]))))
|
||||
|
||||
# bump version number
|
||||
update_versions(packages, new_version)
|
||||
print(fmt("@{gf}Bump version@{reset} of all packages from '@{bf}%s@{reset}' to '@{bf}@{boldon}%s@{boldoff}@{reset}'" % (old_version, new_version)))
|
||||
|
||||
pushed = None
|
||||
if vcs_type in ['svn']:
|
||||
# for svn everything affects the remote repository immediately
|
||||
commands = []
|
||||
commands.append(commit_files(base_path, vcs_type, packages, missing_changelogs_but_forthcoming, tag_name, dry_run=True))
|
||||
commands.append(tag_svn_cmd)
|
||||
if not args.no_push:
|
||||
print(fmt('@{gf}The following commands will be executed to commit the changes and tag the new version:'))
|
||||
else:
|
||||
print(fmt('@{gf}You can use the following commands to manually commit the changes and tag the new version:'))
|
||||
for cmd in commands:
|
||||
print(fmt(' @{bf}@{boldon}%s@{boldoff}' % ' '.join(cmd)))
|
||||
|
||||
if not args.no_push:
|
||||
if not args.non_interactive:
|
||||
# confirm before modifying repository
|
||||
if not prompt_continue('Execute commands which will modify the repository', default=True):
|
||||
pushed = False
|
||||
if pushed is None:
|
||||
commit_files(base_path, vcs_type, packages, missing_changelogs_but_forthcoming, tag_name)
|
||||
tag_repository(base_path, vcs_type, tag_name, args.tag_prefix != '')
|
||||
pushed = True
|
||||
|
||||
else:
|
||||
# for other vcs types the changes are first done locally
|
||||
print(fmt('@{gf}Committing the package.xml files...'))
|
||||
commit_files(base_path, vcs_type, packages, missing_changelogs_but_forthcoming, tag_name)
|
||||
|
||||
print(fmt("@{gf}Creating tag '@{boldon}%s@{boldoff}'..." % (tag_name)))
|
||||
tag_repository(base_path, vcs_type, tag_name, args.tag_prefix != '')
|
||||
|
||||
try:
|
||||
commands = push_changes(base_path, vcs_type, tag_name, dry_run=True)
|
||||
except RuntimeError:
|
||||
print(fmt('@{yf}Warning: could not determine commands to push the changes and tag to the remote repository. Do you have a remote configured for the current branch?'))
|
||||
else:
|
||||
if not args.no_push:
|
||||
print(fmt('@{gf}The following commands will be executed to push the changes and tag to the remote repository:'))
|
||||
else:
|
||||
print(fmt('@{gf}You can use the following commands to manually push the changes to the remote repository:'))
|
||||
for cmd in commands:
|
||||
print(fmt(' @{bf}@{boldon}%s@{boldoff}' % ' '.join(cmd)))
|
||||
|
||||
if not args.no_push:
|
||||
if not args.non_interactive:
|
||||
# confirm commands to push to remote repository
|
||||
if not prompt_continue('Execute commands to push the local commits and tags to the remote repository', default=True):
|
||||
pushed = False
|
||||
if pushed is None:
|
||||
push_changes(base_path, vcs_type, tag_name)
|
||||
pushed = True
|
||||
|
||||
if pushed:
|
||||
print(fmt("@{gf}The source repository has been released successfully. The next step will be '@{boldon}bloom-release@{boldoff}'."))
|
||||
else:
|
||||
msg = 'The release of the source repository has been prepared successfully but the changes have not been pushed yet. ' \
|
||||
"After pushing the changes manually the next step will be '@{boldon}bloom-release@{boldoff}'."
|
||||
if args.no_push or pushed is False:
|
||||
print(fmt('@{yf}%s' % msg))
|
||||
else:
|
||||
raise RuntimeError(fmt('@{rf}%s' % msg))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
"""This script renames the forthcoming section in changelog files with the upcoming version and the current date."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from catkin_pkg.changelog import CHANGELOG_FILENAME, get_changelog_from_path
|
||||
from catkin_pkg.changelog_generator import FORTHCOMING_LABEL
|
||||
from catkin_pkg.package_version import bump_version
|
||||
from catkin_pkg.packages import find_packages, verify_equal_package_versions
|
||||
|
||||
import docutils.core
|
||||
|
||||
|
||||
def get_forthcoming_label(rst):
|
||||
document = docutils.core.publish_doctree(rst)
|
||||
forthcoming_label = None
|
||||
for child in document.children:
|
||||
title = None
|
||||
if isinstance(child, docutils.nodes.subtitle):
|
||||
title = child
|
||||
elif isinstance(child, docutils.nodes.section):
|
||||
section = child
|
||||
if len(section.children) > 0 and isinstance(section.children[0], docutils.nodes.title):
|
||||
title = section.children[0]
|
||||
if title and len(title.children) > 0 and isinstance(title.children[0], docutils.nodes.Text):
|
||||
title_text = title.children[0]
|
||||
if FORTHCOMING_LABEL.lower() in title_text.lower():
|
||||
if forthcoming_label:
|
||||
raise RuntimeError('Found multiple forthcoming sections')
|
||||
forthcoming_label = title_text
|
||||
return forthcoming_label
|
||||
|
||||
|
||||
def rename_section(data, old_label, new_label):
|
||||
valid_section_characters = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
|
||||
|
||||
def replace_section(match):
|
||||
section_char = match.group(2)[0]
|
||||
return new_label + '\n' + section_char * len(new_label)
|
||||
pattern = '^(' + re.escape(old_label) + ')\n([' + re.escape(valid_section_characters) + ']+)$'
|
||||
data, count = re.subn(pattern, replace_section, data, flags=re.MULTILINE)
|
||||
if count == 0:
|
||||
raise RuntimeError('Could not find section')
|
||||
if count > 1:
|
||||
raise RuntimeError('Found multiple matching sections')
|
||||
return data
|
||||
|
||||
|
||||
def main(sysargs=None):
|
||||
parser = argparse.ArgumentParser(description='Tag the forthcoming section in the changelog files with an upcoming version number')
|
||||
parser.add_argument('--bump', choices=('major', 'minor', 'patch'), default='patch', help='Which part of the version number to bump? (default: %(default)s)')
|
||||
args = parser.parse_args(sysargs)
|
||||
|
||||
base_path = '.'
|
||||
|
||||
# find packages
|
||||
packages = find_packages(base_path)
|
||||
if not packages:
|
||||
raise RuntimeError('No packages found')
|
||||
print('Found packages: %s' % ', '.join([p.name for p in packages.values()]))
|
||||
|
||||
# fetch current version and verify that all packages have same version number
|
||||
old_version = verify_equal_package_versions(packages.values())
|
||||
new_version = bump_version(old_version, args.bump)
|
||||
print('Tag version %s' % new_version)
|
||||
|
||||
# check for changelog entries
|
||||
changelogs = []
|
||||
missing_forthcoming = []
|
||||
already_tagged = []
|
||||
for pkg_path, package in packages.items():
|
||||
changelog_path = os.path.join(base_path, pkg_path, CHANGELOG_FILENAME)
|
||||
if not os.path.exists(changelog_path):
|
||||
missing_forthcoming.append(package.name)
|
||||
continue
|
||||
changelog = get_changelog_from_path(changelog_path, package.name)
|
||||
if not changelog:
|
||||
missing_forthcoming.append(package.name)
|
||||
continue
|
||||
# check that forthcoming section exists
|
||||
forthcoming_label = get_forthcoming_label(changelog.rst)
|
||||
if not forthcoming_label:
|
||||
missing_forthcoming.append(package.name)
|
||||
continue
|
||||
# check that new_version section does not exist yet
|
||||
try:
|
||||
changelog.get_content_of_version(new_version)
|
||||
already_tagged.append(package.name)
|
||||
continue
|
||||
except KeyError:
|
||||
pass
|
||||
changelogs.append((package.name, changelog_path, changelog, forthcoming_label))
|
||||
if missing_forthcoming:
|
||||
print('The following packages do not have a forthcoming section in their changelog file: %s' % ', '.join(sorted(missing_forthcoming)), file=sys.stderr)
|
||||
if already_tagged:
|
||||
print("The following packages do already have a section '%s' in their changelog file: %s" % (new_version, ', '.join(sorted(already_tagged))), file=sys.stderr)
|
||||
|
||||
# rename forthcoming sections to new_version including current date
|
||||
new_changelog_data = []
|
||||
new_label = '%s (%s)' % (new_version, datetime.date.today().isoformat())
|
||||
for (pkg_name, changelog_path, changelog, forthcoming_label) in changelogs:
|
||||
print("Renaming section '%s' to '%s' in package '%s'..." % (forthcoming_label, new_label, pkg_name))
|
||||
data = rename_section(changelog.rst, forthcoming_label, new_label)
|
||||
new_changelog_data.append((changelog_path, data))
|
||||
|
||||
print('Writing updated changelog files...')
|
||||
for (changelog_path, data) in new_changelog_data:
|
||||
with open(changelog_path, 'wb') as f:
|
||||
f.write(data.encode('utf-8'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
"""This script tests REP-0132 changelog files."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
import catkin_pkg.changelog
|
||||
from catkin_pkg.changelog import Changelog, CHANGELOG_FILENAME
|
||||
from catkin_pkg.changelog import populate_changelog_from_rst
|
||||
|
||||
|
||||
def main(sysargs=None):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Tests a REP-0132 %s' % CHANGELOG_FILENAME)
|
||||
parser.add_argument(
|
||||
'changelog_file',
|
||||
help='%s file to parse' % CHANGELOG_FILENAME,
|
||||
default='.',
|
||||
nargs='?')
|
||||
|
||||
args = parser.parse_args(sysargs)
|
||||
|
||||
if os.path.isdir(args.changelog_file):
|
||||
changelog_file = os.path.join(args.changelog_file, CHANGELOG_FILENAME)
|
||||
if not os.path.exists(changelog_file):
|
||||
print("No {0} file in given directory: '{1}'"
|
||||
.format(CHANGELOG_FILENAME, args.changelog_file), file=sys.stderr)
|
||||
return 1
|
||||
else:
|
||||
changelog_file = args.changelog_file
|
||||
if not os.path.exists(changelog_file):
|
||||
print("{0} file given does not exist: '{1}'"
|
||||
.format(CHANGELOG_FILENAME, args.changelog_file), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if os.path.basename(changelog_file) != CHANGELOG_FILENAME:
|
||||
print('WARNING: changelog file name should be %s' % CHANGELOG_FILENAME)
|
||||
|
||||
logging.basicConfig()
|
||||
catkin_pkg.changelog.log.setLevel(logging.DEBUG)
|
||||
changelog = Changelog()
|
||||
with open(changelog_file, 'r') as f:
|
||||
print(populate_changelog_from_rst(changelog, f.read()))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2013, Open Source Robotics Foundation, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Open Source Robotics Foundation, Inc. nor
|
||||
# the names of its contributors may be used to endorse or promote
|
||||
# products derived from this software without specific prior
|
||||
# written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
def get_metapackage_cmake_template_path():
|
||||
"""
|
||||
Return the location of the metapackage CMakeLists.txt CMake template.
|
||||
|
||||
:returns: ``str`` location of the metapackage CMakeLists.txt CMake template
|
||||
"""
|
||||
rel_path = os.path.join('templates', 'metapackage.cmake.in')
|
||||
return os.path.join(os.path.dirname(__file__), rel_path)
|
||||
|
||||
|
||||
def configure_file(template_file, environment):
|
||||
"""
|
||||
Evaluate a .in template file used in CMake with configure_file().
|
||||
|
||||
:param template_file: path to the template, ``str``
|
||||
:param environment: dictionary of placeholders to substitute,
|
||||
``dict``
|
||||
:returns: string with evaluates template
|
||||
:raises: KeyError for placeholders in the template which are not
|
||||
in the environment
|
||||
""" # noqa: D402
|
||||
with open(template_file, 'r') as f:
|
||||
template = f.read()
|
||||
return configure_string(template, environment)
|
||||
|
||||
|
||||
def configure_string(template, environment):
|
||||
"""
|
||||
Substitute variables enclosed by @ characters.
|
||||
|
||||
:param template: the template, ``str``
|
||||
:param environment: dictionary of placeholders to substitute,
|
||||
``dict``
|
||||
:returns: string with evaluates template
|
||||
:raises: KeyError for placeholders in the template which are not
|
||||
in the environment
|
||||
"""
|
||||
def substitute(match):
|
||||
var = match.group(0)[1:-1]
|
||||
return environment[var]
|
||||
return re.sub('@[a-zA-Z0-9_]+@', substitute, template)
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
# Copyright 2017 Open Source Robotics Foundation, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import operator
|
||||
|
||||
import pyparsing as pp
|
||||
|
||||
# operatorPrecedence renamed to infixNotation in 1.5.7
|
||||
try:
|
||||
from pyparsing import infixNotation
|
||||
except ImportError:
|
||||
from pyparsing import operatorPrecedence as infixNotation
|
||||
|
||||
|
||||
def evaluate_condition(condition, context):
|
||||
if condition is None:
|
||||
return True
|
||||
expr = _get_condition_expression()
|
||||
try:
|
||||
parse_results = expr.parseString(condition, parseAll=True)
|
||||
except pp.ParseException as e:
|
||||
raise ValueError(
|
||||
"condition '%s' failed to parse: %s" % (condition, e))
|
||||
return parse_results[0](context)
|
||||
|
||||
|
||||
_condition_expression = None
|
||||
|
||||
|
||||
def _get_condition_expression():
|
||||
global _condition_expression
|
||||
if not _condition_expression:
|
||||
operator = pp.Regex('==|!=|>=|>|<=|<').setName('operator')
|
||||
operator.setParseAction(_Operator)
|
||||
|
||||
identifier = pp.Word('$', pp.alphanums + '_', min=2).setName('identifier')
|
||||
identifier.setParseAction(_Identifier)
|
||||
|
||||
value = pp.Word(pp.alphanums + '_-').setName('value')
|
||||
value.setParseAction(_Value)
|
||||
|
||||
double_quoted_value = pp.QuotedString('"').setName(
|
||||
'double_quoted_value')
|
||||
double_quoted_value.setParseAction(_Value)
|
||||
single_quoted_value = pp.QuotedString("'").setName(
|
||||
'single_quoted_value')
|
||||
single_quoted_value.setParseAction(_Value)
|
||||
|
||||
comparison_term = identifier | value | double_quoted_value | \
|
||||
single_quoted_value
|
||||
|
||||
condition = pp.Group(comparison_term + operator + comparison_term).setName('condition')
|
||||
condition.setParseAction(_Condition)
|
||||
|
||||
_condition_expression = infixNotation(
|
||||
condition, [
|
||||
('and', 2, pp.opAssoc.LEFT, _And),
|
||||
('or', 2, pp.opAssoc.LEFT, _Or),
|
||||
])
|
||||
return _condition_expression
|
||||
|
||||
|
||||
class _Operator:
|
||||
operators = {
|
||||
'==': operator.eq,
|
||||
'!=': operator.ne,
|
||||
'<=': operator.le,
|
||||
'<': operator.lt,
|
||||
'>=': operator.ge,
|
||||
'>': operator.gt,
|
||||
}
|
||||
|
||||
def __init__(self, t):
|
||||
self.value = t[0]
|
||||
|
||||
def __call__(self, arg1, arg2, context):
|
||||
assert self.value in self.operators
|
||||
return self.operators[self.value](arg1(context), arg2(context))
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class _Identifier:
|
||||
|
||||
def __init__(self, t):
|
||||
self.value = t[0]
|
||||
|
||||
def __call__(self, context):
|
||||
return str(context.get(self.value[1:], ''))
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class _Value:
|
||||
|
||||
def __init__(self, t):
|
||||
self.value = t[0]
|
||||
|
||||
def __call__(self, context):
|
||||
return self.value
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class _Condition:
|
||||
|
||||
def __init__(self, t):
|
||||
self.value = t[0]
|
||||
|
||||
def __call__(self, context):
|
||||
return self.value[1](self.value[0], self.value[2], context)
|
||||
|
||||
def __str__(self):
|
||||
return ' '.join(map(str, self.value))
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class _BinOp:
|
||||
|
||||
def __init__(self, t):
|
||||
self.args = t[0][0::2]
|
||||
|
||||
def __call__(self, context):
|
||||
return self.evalop(a(context) for a in self.args)
|
||||
|
||||
def __str__(self):
|
||||
sep = ' %s ' % self.reprsymbol
|
||||
return '(' + sep.join(map(str, self.args)) + ')'
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class _And(_BinOp):
|
||||
reprsymbol = 'and'
|
||||
evalop = all
|
||||
|
||||
|
||||
class _Or(_BinOp):
|
||||
reprsymbol = 'or'
|
||||
evalop = any
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# Copyright 2017 Open Source Robotics Foundation, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from catkin_pkg.condition import evaluate_condition
|
||||
|
||||
|
||||
class GroupDependency:
|
||||
__slots__ = [
|
||||
'name',
|
||||
'condition',
|
||||
'evaluated_condition',
|
||||
'members',
|
||||
]
|
||||
|
||||
def __init__(self, name, condition=None, members=None):
|
||||
self.name = name
|
||||
self.condition = condition
|
||||
self.members = members
|
||||
self.evaluated_condition = None
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, GroupDependency):
|
||||
return False
|
||||
return all(getattr(self, attr) == getattr(other, attr)
|
||||
for attr in self.__slots__)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def evaluate_condition(self, context):
|
||||
"""
|
||||
Evaluate the condition.
|
||||
|
||||
The result is also stored in the member variable `evaluated_condition`.
|
||||
|
||||
:param context: A dictionary with key value pairs to replace variables
|
||||
starting with $ in the condition.
|
||||
|
||||
:returns: True if the condition evaluates to True, else False
|
||||
:raises: :exc:`ValueError` if the condition fails to parse
|
||||
"""
|
||||
self.evaluated_condition = evaluate_condition(self.condition, context)
|
||||
return self.evaluated_condition
|
||||
|
||||
def extract_group_members(self, packages):
|
||||
self.members = set()
|
||||
for pkg in packages:
|
||||
for g in pkg.member_of_groups:
|
||||
assert g.evaluated_condition is not None
|
||||
if self.name in (g.name for g in pkg.member_of_groups if g.evaluated_condition):
|
||||
self.members.add(pkg.name)
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# Copyright 2017 Open Source Robotics Foundation, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from catkin_pkg.condition import evaluate_condition
|
||||
|
||||
|
||||
class GroupMembership:
|
||||
__slots__ = [
|
||||
'name',
|
||||
'condition',
|
||||
'evaluated_condition',
|
||||
]
|
||||
|
||||
def __init__(self, name, condition=None):
|
||||
self.name = name
|
||||
self.condition = condition
|
||||
self.evaluated_condition = None
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, GroupMembership):
|
||||
return False
|
||||
return all(getattr(self, attr) == getattr(other, attr)
|
||||
for attr in self.__slots__)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def evaluate_condition(self, context):
|
||||
"""
|
||||
Evaluate the condition.
|
||||
|
||||
The result is also stored in the member variable `evaluated_condition`.
|
||||
|
||||
:param context: A dictionary with key value pairs to replace variables
|
||||
starting with $ in the condition.
|
||||
|
||||
:returns: True if the condition evaluates to True, else False
|
||||
:raises: :exc:`ValueError` if the condition fails to parse
|
||||
"""
|
||||
self.evaluated_condition = evaluate_condition(self.condition, context)
|
||||
return self.evaluated_condition
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2013, Open Source Robotics Foundation, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Open Source Robotics Foundation, Inc. nor
|
||||
# the names of its contributors may be used to endorse or promote
|
||||
# products derived from this software without specific prior
|
||||
# written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
Checks metapackages for compliance with REP-0127.
|
||||
|
||||
Reference: http://ros.org/reps/rep-0127.html#metapackage
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from catkin_pkg.cmake import configure_file
|
||||
from catkin_pkg.cmake import get_metapackage_cmake_template_path
|
||||
|
||||
__author__ = 'William Woodall'
|
||||
__email__ = 'william@osrfoundation.org'
|
||||
__maintainer__ = 'William Woodall'
|
||||
|
||||
DEFINITION_URL = 'http://ros.org/reps/rep-0127.html#metapackage'
|
||||
|
||||
|
||||
class InvalidMetapackage(Exception):
|
||||
|
||||
def __init__(self, msg, path, package):
|
||||
self.path = path
|
||||
self.package = package
|
||||
Exception.__init__(self, "Metapackage '%s': %s" % (package.name, msg))
|
||||
|
||||
|
||||
def get_expected_cmakelists_txt(metapackage_name):
|
||||
"""
|
||||
Return the expected boilerplate CMakeLists.txt file for a metapackage.
|
||||
|
||||
:param metapackage_name: name of the metapackage
|
||||
:type metapackage_name: str
|
||||
:returns: expected CMakeLists.txt file
|
||||
:rtype: str
|
||||
"""
|
||||
env = {
|
||||
'name': metapackage_name,
|
||||
'metapackage_arguments': ''
|
||||
}
|
||||
return configure_file(get_metapackage_cmake_template_path(), env)
|
||||
|
||||
|
||||
def has_cmakelists_txt(path):
|
||||
"""
|
||||
Return True if the given path contains a CMakeLists.txt, otherwise False.
|
||||
|
||||
:param path: path to folder potentially containing CMakeLists.txt
|
||||
:type path: str
|
||||
:returns: True if path contains CMakeLists.txt, else False
|
||||
:rtype: bool
|
||||
"""
|
||||
cmakelists_txt_path = os.path.join(path, 'CMakeLists.txt')
|
||||
return os.path.isfile(cmakelists_txt_path)
|
||||
|
||||
|
||||
def get_cmakelists_txt(path):
|
||||
"""
|
||||
Fetch the CMakeLists.txt from a given path.
|
||||
|
||||
:param path: path to the folder containing the CMakeLists.txt
|
||||
:type path: str
|
||||
:returns: contents of CMakeLists.txt file in given path
|
||||
:rtype: str
|
||||
:raises OSError: if there is no CMakeLists.txt in given path
|
||||
"""
|
||||
cmakelists_txt_path = os.path.join(path, 'CMakeLists.txt')
|
||||
with open(cmakelists_txt_path, 'r') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def has_valid_cmakelists_txt(path, metapackage_name):
|
||||
"""
|
||||
Return True if the given path contains a valid CMakeLists.txt, otherwise False.
|
||||
|
||||
A valid CMakeLists.txt for a metapackage is defined by REP-0127
|
||||
|
||||
:param path: path to folder containing CMakeLists.txt
|
||||
:type path: str
|
||||
:param metapackage_name: name of the metapackage being tested
|
||||
:type metapackage_name: str
|
||||
:returns: True if the path contains a valid CMakeLists.txt, else False
|
||||
:rtype: bool
|
||||
:raises OSError: if there is no CMakeLists.txt in given path
|
||||
"""
|
||||
cmakelists_txt = get_cmakelists_txt(path)
|
||||
expected = get_expected_cmakelists_txt(metapackage_name)
|
||||
prefix, suffix = expected.split('2.8.3', 1)
|
||||
if not cmakelists_txt.startswith(prefix):
|
||||
return False
|
||||
if not cmakelists_txt.endswith(suffix):
|
||||
return False
|
||||
version = cmakelists_txt[len(prefix):-len(suffix)]
|
||||
return re.match(r'^\d+\.\d+\.\d+$', version)
|
||||
|
||||
|
||||
def validate_metapackage(path, package):
|
||||
"""
|
||||
Validate the given package (catkin_pkg.package.Package) as a metapackage.
|
||||
|
||||
This validates the metapackage against the definition from REP-0127
|
||||
|
||||
:param path: directory of the package being checked
|
||||
:type path: str
|
||||
:param package: package to be validated
|
||||
:type package: :py:class:`catkin_pkg.package.Package`
|
||||
:raises InvalidMetapackage: if package is not a valid metapackage
|
||||
:raises OSError: if there is not package.xml at the given path
|
||||
"""
|
||||
# Is there actually a package at the given path, else raise
|
||||
# Cannot do package_exists_at from catkin_pkg.packages because of circular dep
|
||||
if not os.path.isdir(path) or not os.path.isfile(os.path.join(path, 'package.xml')):
|
||||
raise OSError("No package.xml found at path: '%s'" % path)
|
||||
# Is it a metapackage, else raise
|
||||
if not package.is_metapackage():
|
||||
raise InvalidMetapackage('No <metapackage/> tag in <export> section of package.xml', path, package)
|
||||
# Is there a CMakeLists.txt, else raise
|
||||
if not has_cmakelists_txt(path):
|
||||
raise InvalidMetapackage('No CMakeLists.txt', path, package)
|
||||
# Is the CMakeLists.txt correct, else raise
|
||||
if not has_valid_cmakelists_txt(path, package.name):
|
||||
expected = get_expected_cmakelists_txt(package.name)
|
||||
expected = expected.replace('2.8.3', '<version x.y.z>')
|
||||
raise InvalidMetapackage("""\
|
||||
Invalid CMakeLists.txt
|
||||
Expected:
|
||||
<<<%s>>>
|
||||
Got:
|
||||
<<<%s>>>""" % (expected, get_cmakelists_txt(path)), path, package
|
||||
)
|
||||
# Does it buildtool depend on catkin, else raise
|
||||
if not package.has_buildtool_depend_on_catkin():
|
||||
raise InvalidMetapackage('No buildtool dependency on catkin', path, package)
|
||||
# Does it have only run depends, else raise
|
||||
if package.has_invalid_metapackage_dependencies():
|
||||
raise InvalidMetapackage(
|
||||
'Has build, buildtool, and/or test depends, but only run depends are allowed (except buildtool catkin)',
|
||||
path, package)
|
||||
|
|
@ -0,0 +1,876 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2012, Willow Garage, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Willow Garage, Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived
|
||||
# from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Library for parsing package.xml and providing an object representation."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from copy import deepcopy
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import xml.dom.minidom as dom
|
||||
from xml.parsers.expat import ExpatError
|
||||
|
||||
from catkin_pkg.condition import evaluate_condition
|
||||
|
||||
PACKAGE_MANIFEST_FILENAME = 'package.xml'
|
||||
PACKAGE_MANIFEST_SCHEMA_URLS = [
|
||||
'http://download.ros.org/schema/package_format1.xsd',
|
||||
'http://download.ros.org/schema/package_format2.xsd',
|
||||
'http://download.ros.org/schema/package_format3.xsd',
|
||||
]
|
||||
|
||||
|
||||
class Package(object):
|
||||
"""Object representation of a package manifest file."""
|
||||
|
||||
__slots__ = [
|
||||
'package_format',
|
||||
'name',
|
||||
'version',
|
||||
'version_compatibility',
|
||||
'description',
|
||||
'plaintext_description',
|
||||
'maintainers',
|
||||
'licenses',
|
||||
'urls',
|
||||
'authors',
|
||||
'build_depends',
|
||||
'buildtool_depends',
|
||||
'build_export_depends',
|
||||
'buildtool_export_depends',
|
||||
'exec_depends',
|
||||
'test_depends',
|
||||
'doc_depends',
|
||||
'conflicts',
|
||||
'replaces',
|
||||
'group_depends',
|
||||
'member_of_groups',
|
||||
'exports',
|
||||
'filename'
|
||||
]
|
||||
|
||||
def __init__(self, filename=None, **kwargs):
|
||||
"""
|
||||
Initialize Package.
|
||||
|
||||
:param filename: location of package.xml. Necessary if
|
||||
converting ``${prefix}`` in ``<export>`` values, ``str``.
|
||||
"""
|
||||
# initialize all slots ending with "s" with lists, all other with plain values
|
||||
for attr in self.__slots__:
|
||||
if attr.endswith('s'):
|
||||
value = list(kwargs[attr]) if attr in kwargs else []
|
||||
setattr(self, attr, value)
|
||||
else:
|
||||
value = kwargs[attr] if attr in kwargs else None
|
||||
setattr(self, attr, value)
|
||||
if 'depends' in kwargs:
|
||||
for d in kwargs['depends']:
|
||||
for slot in [self.build_depends, self.build_export_depends, self.exec_depends]:
|
||||
if d not in slot:
|
||||
slot.append(deepcopy(d))
|
||||
del kwargs['depends']
|
||||
if 'run_depends' in kwargs:
|
||||
for d in kwargs['run_depends']:
|
||||
for slot in [self.build_export_depends, self.exec_depends]:
|
||||
if d not in slot:
|
||||
slot.append(deepcopy(d))
|
||||
del kwargs['run_depends']
|
||||
self.filename = filename
|
||||
self.licenses = [license_ if isinstance(license_, License) else License(license_) for license_ in self.licenses]
|
||||
# verify that no unknown keywords are passed
|
||||
unknown = set(kwargs.keys()).difference(self.__slots__)
|
||||
if unknown:
|
||||
raise TypeError('Unknown properties: %s' % ', '.join(unknown))
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == 'run_depends':
|
||||
# merge different dependencies if they are not exactly equal
|
||||
# potentially having the same dependency name multiple times with different attributes
|
||||
run_depends = []
|
||||
[run_depends.append(deepcopy(d)) for d in self.exec_depends + self.build_export_depends if d not in run_depends]
|
||||
return run_depends
|
||||
raise AttributeError(name)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self.__slots__ + ['run_depends']:
|
||||
return getattr(self, key)
|
||||
raise KeyError('Unknown key "%s"' % key)
|
||||
|
||||
def __iter__(self):
|
||||
for slot in self.__slots__:
|
||||
yield slot
|
||||
|
||||
def __str__(self):
|
||||
data = {}
|
||||
for attr in self.__slots__:
|
||||
data[attr] = getattr(self, attr)
|
||||
return str(data)
|
||||
|
||||
def has_buildtool_depend_on_catkin(self):
|
||||
"""
|
||||
Return True if this Package buildtool depends on catkin, otherwise False.
|
||||
|
||||
:returns: True if the given package buildtool depends on catkin
|
||||
:rtype: bool
|
||||
"""
|
||||
return 'catkin' in (d.name for d in self.buildtool_depends)
|
||||
|
||||
def get_build_type(self):
|
||||
"""
|
||||
Return value of export/build_type element, or 'catkin' if unspecified.
|
||||
|
||||
:returns: package build type
|
||||
:rtype: str
|
||||
:raises: :exc:`InvalidPackage`
|
||||
"""
|
||||
# for backward compatibility a build type without an evaluated
|
||||
# condition is still being considered (i.e. evaluated_condition is None)
|
||||
build_type_exports = [
|
||||
e.content for e in self.exports
|
||||
if e.tagname == 'build_type' and e.evaluated_condition is not False]
|
||||
if not build_type_exports:
|
||||
return 'catkin'
|
||||
if len(build_type_exports) == 1:
|
||||
return build_type_exports[0]
|
||||
raise InvalidPackage('Only one <build_type> element is permitted.', self.filename)
|
||||
|
||||
def get_unconditional_build_types(self):
|
||||
"""
|
||||
Return values of export/build_type elements without conditional filtering, or ['catkin'] if unspecified.
|
||||
|
||||
:returns: package build types
|
||||
:rtype: List[str]
|
||||
"""
|
||||
build_type_exports = [e.content for e in self.exports if e.tagname == 'build_type']
|
||||
if not build_type_exports:
|
||||
return ['catkin']
|
||||
return build_type_exports
|
||||
|
||||
def has_invalid_metapackage_dependencies(self):
|
||||
"""
|
||||
Return True if this package has invalid dependencies for a metapackage.
|
||||
|
||||
This is defined by REP-0127 as any non-run_depends dependencies other then a buildtool_depend on catkin.
|
||||
|
||||
:returns: True if the given package has any invalid dependencies, otherwise False
|
||||
:rtype: bool
|
||||
"""
|
||||
buildtool_depends = [d.name for d in self.buildtool_depends if d.name != 'catkin']
|
||||
return len(self.build_depends + buildtool_depends + self.test_depends) > 0
|
||||
|
||||
def is_metapackage(self):
|
||||
"""
|
||||
Return True if this pacakge is a metapackage, otherwise False.
|
||||
|
||||
:returns: True if metapackage, else False
|
||||
:rtype: bool
|
||||
"""
|
||||
return 'metapackage' in (e.tagname for e in self.exports)
|
||||
|
||||
def evaluate_conditions(self, context):
|
||||
"""
|
||||
Evaluate the conditions of all dependencies and memberships.
|
||||
|
||||
:param context: A dictionary with key value pairs to replace variables
|
||||
starting with $ in the condition.
|
||||
:raises: :exc:`ValueError` if any condition fails to parse
|
||||
"""
|
||||
for attr in (
|
||||
'build_depends',
|
||||
'buildtool_depends',
|
||||
'build_export_depends',
|
||||
'buildtool_export_depends',
|
||||
'exec_depends',
|
||||
'test_depends',
|
||||
'doc_depends',
|
||||
'conflicts',
|
||||
'replaces',
|
||||
'group_depends',
|
||||
'member_of_groups',
|
||||
'exports',
|
||||
):
|
||||
conditionals = getattr(self, attr)
|
||||
for conditional in conditionals:
|
||||
conditional.evaluate_condition(context)
|
||||
|
||||
def validate(self, warnings=None):
|
||||
"""
|
||||
Make sure all standards for packages are met.
|
||||
|
||||
:param package: Package to check
|
||||
:param warnings: Print warnings if None or return them in the given list
|
||||
:raises InvalidPackage: in case validation fails
|
||||
"""
|
||||
errors = []
|
||||
new_warnings = []
|
||||
|
||||
if self.package_format:
|
||||
if not re.match('^[1-9][0-9]*$', str(self.package_format)):
|
||||
errors.append('The "format" attribute of the package must contain a positive integer if present')
|
||||
|
||||
if not self.name:
|
||||
errors.append('Package name must not be empty')
|
||||
# accepting upper case letters and hyphens only for backward compatibility
|
||||
if not re.match('^[a-zA-Z0-9][a-zA-Z0-9_-]*$', self.name):
|
||||
errors.append('Package name "%s" does not follow naming conventions' % self.name)
|
||||
else:
|
||||
if not re.match('^[a-z][a-z0-9_-]*$', self.name):
|
||||
new_warnings.append(
|
||||
'Package name "%s" does not follow the naming conventions. It should start with '
|
||||
'a lower case letter and only contain lower case letters, digits, underscores, and dashes.' % self.name)
|
||||
|
||||
version_regexp = r'^[0-9]+\.[0-9]+\.[0-9]+$'
|
||||
if not self.version:
|
||||
errors.append('Package version must not be empty')
|
||||
elif not re.match(version_regexp, self.version):
|
||||
errors.append('Package version "%s" does not follow version conventions' % self.version)
|
||||
elif not re.match(r'^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$', self.version):
|
||||
new_warnings.append('Package "%s" does not follow the version conventions. It should not contain leading zeros (unless the number is 0).' % self.name)
|
||||
if self.version_compatibility:
|
||||
if not re.match(version_regexp, self.version_compatibility):
|
||||
errors.append(
|
||||
"Package compatibility version '%s' does not follow "
|
||||
'version conventions' % self.version_compatibility)
|
||||
|
||||
if not self.description:
|
||||
errors.append('Package description must not be empty')
|
||||
|
||||
if not self.maintainers:
|
||||
errors.append("Package '{0}' must declare at least one maintainer".format(self.name))
|
||||
for maintainer in self.maintainers:
|
||||
try:
|
||||
maintainer.validate()
|
||||
except InvalidPackage as e:
|
||||
errors.append(e.msg)
|
||||
if not maintainer.email:
|
||||
errors.append('Maintainers must have an email address')
|
||||
|
||||
if not self.licenses:
|
||||
errors.append('The package node must contain at least one "license" tag')
|
||||
if [license_ for license_ in self.licenses if not license_.strip()]:
|
||||
errors.append('The license tag must neither be empty nor only contain whitespaces')
|
||||
|
||||
if self.authors is not None:
|
||||
for author in self.authors:
|
||||
try:
|
||||
author.validate()
|
||||
except InvalidPackage as e:
|
||||
errors.append(e.msg)
|
||||
|
||||
dep_types = {
|
||||
'build': self.build_depends,
|
||||
'buildtool': self.buildtool_depends,
|
||||
'build_export': self.build_export_depends,
|
||||
'buildtool_export': self.buildtool_export_depends,
|
||||
'exec': self.exec_depends,
|
||||
'test': self.test_depends,
|
||||
'doc': self.doc_depends
|
||||
}
|
||||
for dep_type, depends in dep_types.items():
|
||||
for depend in depends:
|
||||
if depend.name == self.name:
|
||||
errors.append('The package "%s" must not "%s_depend" on a package with the same name as this package' % (self.name, dep_type))
|
||||
|
||||
if (
|
||||
set([d.name for d in self.group_depends]) &
|
||||
set([g.name for g in self.member_of_groups])
|
||||
):
|
||||
errors.append(
|
||||
"The package must not 'group_depend' on a package which it "
|
||||
'also declares to be a member of')
|
||||
|
||||
if self.is_metapackage():
|
||||
if not self.has_buildtool_depend_on_catkin():
|
||||
# TODO escalate to error in the future, or use metapackage.validate_metapackage
|
||||
new_warnings.append('Metapackage "%s" must buildtool_depend on catkin.' % self.name)
|
||||
if self.has_invalid_metapackage_dependencies():
|
||||
new_warnings.append('Metapackage "%s" should not have other dependencies besides a '
|
||||
'buildtool_depend on catkin and %s.' %
|
||||
(self.name, 'run_depends' if self.package_format == 1 else 'exec_depends'))
|
||||
|
||||
for warning in new_warnings:
|
||||
if warnings is None:
|
||||
print('WARNING: ' + warning, file=sys.stderr)
|
||||
elif warning not in warnings:
|
||||
warnings.append(warning)
|
||||
|
||||
if errors:
|
||||
raise InvalidPackage('\n'.join(errors), self.filename)
|
||||
|
||||
|
||||
class Dependency(object):
|
||||
__slots__ = [
|
||||
'name',
|
||||
'version_lt', 'version_lte', 'version_eq', 'version_gte', 'version_gt',
|
||||
'condition',
|
||||
'evaluated_condition',
|
||||
]
|
||||
|
||||
def __init__(self, name, **kwargs):
|
||||
self.evaluated_condition = None
|
||||
for attr in self.__slots__:
|
||||
value = kwargs[attr] if attr in kwargs else None
|
||||
setattr(self, attr, value)
|
||||
self.name = name
|
||||
# verify that no unknown keywords are passed
|
||||
unknown = set(kwargs.keys()).difference(self.__slots__)
|
||||
if unknown:
|
||||
raise TypeError('Unknown properties: %s' % ', '.join(unknown))
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Dependency):
|
||||
return False
|
||||
return all(getattr(self, attr) == getattr(other, attr) for attr in self.__slots__ if attr != 'evaluated_condition')
|
||||
|
||||
def __hash__(self):
|
||||
return hash(tuple(getattr(self, slot) for slot in self.__slots__))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
kv = []
|
||||
for slot in self.__slots__:
|
||||
attr = getattr(self, slot, None)
|
||||
if attr is not None:
|
||||
kv.append('{}={!r}'.format(slot, attr))
|
||||
return '{}({})'.format(self.__class__.__name__, ', '.join(kv))
|
||||
|
||||
def evaluate_condition(self, context):
|
||||
"""
|
||||
Evaluate the condition.
|
||||
|
||||
The result is also stored in the member variable `evaluated_condition`.
|
||||
|
||||
:param context: A dictionary with key value pairs to replace variables
|
||||
starting with $ in the condition.
|
||||
|
||||
:returns: True if the condition evaluates to True, else False
|
||||
:raises: :exc:`ValueError` if the condition fails to parse
|
||||
"""
|
||||
self.evaluated_condition = evaluate_condition(self.condition, context)
|
||||
return self.evaluated_condition
|
||||
|
||||
|
||||
class Export(object):
|
||||
__slots__ = ['tagname', 'attributes', 'content', 'evaluated_condition']
|
||||
|
||||
def __init__(self, tagname, content=None):
|
||||
self.tagname = tagname
|
||||
self.attributes = {}
|
||||
self.content = content
|
||||
self.evaluated_condition = None
|
||||
|
||||
def __str__(self):
|
||||
txt = '<%s' % self.tagname
|
||||
for key in sorted(self.attributes.keys()):
|
||||
txt += ' %s="%s"' % (key, self.attributes[key])
|
||||
if self.content:
|
||||
txt += '>%s</%s>' % (self.content, self.tagname)
|
||||
else:
|
||||
txt += '/>'
|
||||
return txt
|
||||
|
||||
def evaluate_condition(self, context):
|
||||
"""
|
||||
Evaluate the condition.
|
||||
|
||||
The result is also stored in the member variable `evaluated_condition`.
|
||||
|
||||
:param context: A dictionary with key value pairs to replace variables
|
||||
starting with $ in the condition.
|
||||
|
||||
:returns: True if the condition evaluates to True, else False
|
||||
:raises: :exc:`ValueError` if the condition fails to parse
|
||||
"""
|
||||
self.evaluated_condition = evaluate_condition(self.attributes.get('condition'), context)
|
||||
return self.evaluated_condition
|
||||
|
||||
|
||||
# Subclassing ``str`` to keep backward compatibility.
|
||||
class License(str):
|
||||
|
||||
def __new__(cls, value, file_=None):
|
||||
obj = str.__new__(cls, str(value))
|
||||
obj.file = file_
|
||||
return obj
|
||||
|
||||
|
||||
class Person(object):
|
||||
__slots__ = ['name', 'email']
|
||||
|
||||
def __init__(self, name, email=None):
|
||||
self.name = name
|
||||
self.email = email
|
||||
|
||||
def __str__(self):
|
||||
name = self.name
|
||||
if not isinstance(name, str):
|
||||
name = name.encode('utf-8')
|
||||
if self.email is not None:
|
||||
return '%s <%s>' % (name, self.email)
|
||||
else:
|
||||
return '%s' % name
|
||||
|
||||
def validate(self):
|
||||
if self.email is None:
|
||||
return
|
||||
if not re.match(r'^[-a-zA-Z0-9_%+]+(\.[-a-zA-Z0-9_%+]+)*@[-a-zA-Z0-9%]+(\.[-a-zA-Z0-9%]+)*\.[a-zA-Z]{2,}$', self.email):
|
||||
raise InvalidPackage('Invalid email "%s" for person "%s"' % (self.email, self.name))
|
||||
|
||||
|
||||
class Url(object):
|
||||
__slots__ = ['url', 'type']
|
||||
|
||||
def __init__(self, url, type_=None):
|
||||
self.url = url
|
||||
self.type = type_
|
||||
|
||||
def __str__(self):
|
||||
return self.url
|
||||
|
||||
|
||||
def parse_package_for_distutils(path=None):
|
||||
print('WARNING: %s/setup.py: catkin_pkg.package.parse_package_for_distutils() is deprecated. Please use catkin_pkg.python_setup.generate_distutils_setup(**kwargs) instead.' %
|
||||
os.path.basename(os.path.abspath('.')))
|
||||
from .python_setup import generate_distutils_setup
|
||||
data = {}
|
||||
if path is not None:
|
||||
data['package_xml_path'] = path
|
||||
return generate_distutils_setup(**data)
|
||||
|
||||
|
||||
class InvalidPackage(Exception):
|
||||
|
||||
def __init__(self, msg, package_path=None):
|
||||
self.msg = msg
|
||||
self.package_path = package_path
|
||||
Exception.__init__(self, self.msg)
|
||||
|
||||
def __str__(self):
|
||||
result = '' if not self.package_path else "Error(s) in package '%s':\n" % self.package_path
|
||||
return result + Exception.__str__(self)
|
||||
|
||||
|
||||
def package_exists_at(path):
|
||||
"""
|
||||
Check that a package exists at the given path.
|
||||
|
||||
:param path: path to a package
|
||||
:type path: str
|
||||
:returns: True if package exists in given path, else False
|
||||
:rtype: bool
|
||||
"""
|
||||
return os.path.isdir(path) and os.path.isfile(os.path.join(path, PACKAGE_MANIFEST_FILENAME))
|
||||
|
||||
|
||||
def _get_package_xml(path):
|
||||
"""
|
||||
Get xml of package manifest.
|
||||
|
||||
:param path: The path of the package.xml file, it may or may not
|
||||
include the filename
|
||||
|
||||
:returns: a tuple with the xml as well as the path of the read file
|
||||
:raises: :exc:`IOError`
|
||||
"""
|
||||
if os.path.isfile(path):
|
||||
filename = path
|
||||
elif package_exists_at(path):
|
||||
filename = os.path.join(path, PACKAGE_MANIFEST_FILENAME)
|
||||
if not os.path.isfile(filename):
|
||||
raise IOError('Directory "%s" does not contain a "%s"' % (path, PACKAGE_MANIFEST_FILENAME))
|
||||
else:
|
||||
raise IOError('Path "%s" is neither a directory containing a "%s" file nor a file' % (path, PACKAGE_MANIFEST_FILENAME))
|
||||
|
||||
# Force utf8 encoding for python3.
|
||||
# This way unicode files can still be processed on non-unicode locales.
|
||||
kwargs = {}
|
||||
if sys.version_info[0] >= 3:
|
||||
kwargs['encoding'] = 'utf8'
|
||||
|
||||
with open(filename, 'r', **kwargs) as f:
|
||||
return f.read(), filename
|
||||
|
||||
|
||||
def has_ros_schema_reference(path):
|
||||
"""
|
||||
Check if the XML file contains a processing instruction referencing a ROS package manifest schema.
|
||||
|
||||
:param path: The path of the package.xml file, it may or may not
|
||||
include the filename
|
||||
:type path: str
|
||||
:returns: True if it contains the known reference, else False
|
||||
:rtype: bool
|
||||
:raises: :exc:`IOError`
|
||||
"""
|
||||
xml, _ = _get_package_xml(path)
|
||||
return has_ros_schema_reference_string(xml)
|
||||
|
||||
|
||||
def has_ros_schema_reference_string(data):
|
||||
"""
|
||||
Check if the XML data contains a processing instruction referencing a ROS package manifest schema.
|
||||
|
||||
:param data: package.xml contents
|
||||
:type data: str
|
||||
:returns: True if it contains the known reference, else False
|
||||
:rtype: bool
|
||||
"""
|
||||
if sys.version_info[0] == 2 and not isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
try:
|
||||
root = dom.parseString(data)
|
||||
except ExpatError:
|
||||
# invalid XML
|
||||
return False
|
||||
|
||||
for child in root.childNodes:
|
||||
if child.nodeType == child.PROCESSING_INSTRUCTION_NODE:
|
||||
if child.target == 'xml-model':
|
||||
# extract schema url from "xml-model" processing instruction
|
||||
schema_url = re.search(r'href="([A-Za-z0-9\._/:]*)"', child.data).group(1)
|
||||
if schema_url in PACKAGE_MANIFEST_SCHEMA_URLS:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def parse_package(path, warnings=None):
|
||||
"""
|
||||
Parse package manifest.
|
||||
|
||||
:param path: The path of the package.xml file, it may or may not
|
||||
include the filename
|
||||
:param warnings: Print warnings if None or return them in the given list
|
||||
|
||||
:returns: return :class:`Package` instance, populated with parsed fields
|
||||
:raises: :exc:`InvalidPackage`
|
||||
:raises: :exc:`IOError`
|
||||
"""
|
||||
xml, filename = _get_package_xml(path)
|
||||
return parse_package_string(xml, filename, warnings=warnings)
|
||||
|
||||
|
||||
def _check_known_attributes(node, known):
|
||||
if node.hasAttributes():
|
||||
attrs = map(str, node.attributes.keys())
|
||||
# colon is the namespace separator in attributes, xmlns can be added to any tag
|
||||
unknown_attrs = [attr for attr in attrs if not (attr in known or attr == 'xmlns' or ':' in attr)]
|
||||
if unknown_attrs:
|
||||
return ['The "%s" tag must not have the following attributes: %s' % (node.tagName, ', '.join(unknown_attrs))]
|
||||
return []
|
||||
|
||||
|
||||
def parse_package_string(data, filename=None, warnings=None):
|
||||
"""
|
||||
Parse package.xml string contents.
|
||||
|
||||
:param data: package.xml contents, ``str``
|
||||
:param filename: full file path for debugging, ``str``
|
||||
:param warnings: Print warnings if None or return them in the given list
|
||||
:returns: return parsed :class:`Package`
|
||||
:raises: :exc:`InvalidPackage`
|
||||
"""
|
||||
if sys.version_info[0] == 2 and not isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
try:
|
||||
root = dom.parseString(data)
|
||||
except ExpatError as ex:
|
||||
raise InvalidPackage('The manifest contains invalid XML:\n%s' % ex, filename)
|
||||
|
||||
pkg = Package(filename)
|
||||
|
||||
# verify unique root node
|
||||
nodes = _get_nodes(root, 'package')
|
||||
if len(nodes) != 1:
|
||||
raise InvalidPackage('The manifest must contain a single "package" root tag', filename)
|
||||
root = nodes[0]
|
||||
|
||||
# format attribute
|
||||
value = _get_node_attr(root, 'format', default=1)
|
||||
pkg.package_format = int(value)
|
||||
assert pkg.package_format in (1, 2, 3), \
|
||||
"Unable to handle package.xml format version '%d', please update catkin_pkg " \
|
||||
'(e.g. on Ubuntu/Debian use: sudo apt-get update && sudo apt-get install --only-upgrade python-catkin-pkg)' % pkg.package_format
|
||||
|
||||
# name
|
||||
pkg.name = _get_node_value(_get_node(root, 'name', filename))
|
||||
|
||||
# version and optional compatibility
|
||||
version_node = _get_node(root, 'version', filename)
|
||||
pkg.version = _get_node_value(version_node)
|
||||
pkg.version_compatibility = _get_node_attr(
|
||||
version_node, 'compatibility', default=None)
|
||||
|
||||
# description
|
||||
pkg.description = _get_node_value(_get_node(root, 'description', filename), allow_xml=True, apply_str=False)
|
||||
pkg.plaintext_description = re.sub(' +(\n+) +', r'\1', _get_node_text(_get_node(root, 'description', filename)), flags=re.MULTILINE)
|
||||
|
||||
# at least one maintainer, all must have email
|
||||
maintainers = _get_nodes(root, 'maintainer')
|
||||
for node in maintainers:
|
||||
pkg.maintainers.append(Person(
|
||||
_get_node_value(node, apply_str=False),
|
||||
_get_node_attr(node, 'email')
|
||||
))
|
||||
|
||||
# urls with optional type
|
||||
urls = _get_nodes(root, 'url')
|
||||
for node in urls:
|
||||
pkg.urls.append(Url(
|
||||
_get_node_value(node),
|
||||
_get_node_attr(node, 'type', default='website')
|
||||
))
|
||||
|
||||
# authors with optional email
|
||||
authors = _get_nodes(root, 'author')
|
||||
for node in authors:
|
||||
pkg.authors.append(Person(
|
||||
_get_node_value(node, apply_str=False),
|
||||
_get_node_attr(node, 'email', default=None)
|
||||
))
|
||||
|
||||
# at least one license
|
||||
licenses = _get_nodes(root, 'license')
|
||||
for node in licenses:
|
||||
pkg.licenses.append(License(
|
||||
_get_node_value(node),
|
||||
_get_node_attr(node, 'file', default=None)
|
||||
))
|
||||
|
||||
errors = []
|
||||
# dependencies and relationships
|
||||
pkg.build_depends = _get_dependencies(root, 'build_depend')
|
||||
pkg.buildtool_depends = _get_dependencies(root, 'buildtool_depend')
|
||||
if pkg.package_format == 1:
|
||||
run_depends = _get_dependencies(root, 'run_depend')
|
||||
for d in run_depends:
|
||||
pkg.build_export_depends.append(deepcopy(d))
|
||||
pkg.exec_depends.append(deepcopy(d))
|
||||
if pkg.package_format != 1:
|
||||
pkg.build_export_depends = _get_dependencies(root, 'build_export_depend')
|
||||
pkg.buildtool_export_depends = _get_dependencies(root, 'buildtool_export_depend')
|
||||
pkg.exec_depends = _get_dependencies(root, 'exec_depend')
|
||||
depends = _get_dependencies(root, 'depend')
|
||||
for dep in depends:
|
||||
# check for collisions with specific dependencies
|
||||
same_build_depends = ['build_depend' for d in pkg.build_depends if d == dep]
|
||||
same_build_export_depends = ['build_export_depend' for d in pkg.build_export_depends if d == dep]
|
||||
same_exec_depends = ['exec_depend' for d in pkg.exec_depends if d == dep]
|
||||
if same_build_depends or same_build_export_depends or same_exec_depends:
|
||||
errors.append("The generic dependency on '%s' is redundant with: %s" % (dep.name, ', '.join(same_build_depends + same_build_export_depends + same_exec_depends)))
|
||||
# only append non-duplicates
|
||||
if not same_build_depends:
|
||||
pkg.build_depends.append(deepcopy(dep))
|
||||
if not same_build_export_depends:
|
||||
pkg.build_export_depends.append(deepcopy(dep))
|
||||
if not same_exec_depends:
|
||||
pkg.exec_depends.append(deepcopy(dep))
|
||||
pkg.doc_depends = _get_dependencies(root, 'doc_depend')
|
||||
pkg.test_depends = _get_dependencies(root, 'test_depend')
|
||||
pkg.conflicts = _get_dependencies(root, 'conflict')
|
||||
pkg.replaces = _get_dependencies(root, 'replace')
|
||||
|
||||
# group dependencies and memberships
|
||||
pkg.group_depends = _get_group_dependencies(root, 'group_depend')
|
||||
pkg.member_of_groups = _get_group_memberships(root, 'member_of_group')
|
||||
|
||||
if pkg.package_format == 1:
|
||||
for test_depend in pkg.test_depends:
|
||||
same_build_depends = ['build_depend' for d in pkg.build_depends if d == test_depend]
|
||||
same_run_depends = ['run_depend' for d in pkg.run_depends if d == test_depend]
|
||||
if same_build_depends or same_run_depends:
|
||||
errors.append('The test dependency on "%s" is redundant with: %s' % (test_depend.name, ', '.join(same_build_depends + same_run_depends)))
|
||||
|
||||
# exports
|
||||
export_node = _get_optional_node(root, 'export', filename)
|
||||
if export_node is not None:
|
||||
exports = []
|
||||
for node in [n for n in export_node.childNodes if n.nodeType == n.ELEMENT_NODE]:
|
||||
export = Export(str(node.tagName), _get_node_value(node, allow_xml=True))
|
||||
for key, value in node.attributes.items():
|
||||
export.attributes[str(key)] = str(value)
|
||||
exports.append(export)
|
||||
pkg.exports = exports
|
||||
|
||||
# verify that no unsupported tags and attributes are present
|
||||
errors += _check_known_attributes(root, ['format'])
|
||||
depend_attributes = ['version_lt', 'version_lte', 'version_eq', 'version_gte', 'version_gt']
|
||||
if pkg.package_format > 2:
|
||||
depend_attributes.append('condition')
|
||||
known = {
|
||||
'name': [],
|
||||
'version': ['compatibility'],
|
||||
'description': [],
|
||||
'maintainer': ['email'],
|
||||
'license': [],
|
||||
'url': ['type'],
|
||||
'author': ['email'],
|
||||
'build_depend': depend_attributes,
|
||||
'buildtool_depend': depend_attributes,
|
||||
'test_depend': depend_attributes,
|
||||
'conflict': depend_attributes,
|
||||
'replace': depend_attributes,
|
||||
'export': [],
|
||||
}
|
||||
if pkg.package_format == 1:
|
||||
known.update({
|
||||
'run_depend': depend_attributes,
|
||||
})
|
||||
if pkg.package_format != 1:
|
||||
known.update({
|
||||
'build_export_depend': depend_attributes,
|
||||
'buildtool_export_depend': depend_attributes,
|
||||
'depend': depend_attributes,
|
||||
'exec_depend': depend_attributes,
|
||||
'doc_depend': depend_attributes,
|
||||
})
|
||||
if pkg.package_format > 2:
|
||||
known.update({
|
||||
'group_depend': ['condition'],
|
||||
'member_of_group': ['condition']
|
||||
})
|
||||
if pkg.package_format > 2:
|
||||
known.update({
|
||||
'license': ['file'],
|
||||
})
|
||||
nodes = [n for n in root.childNodes if n.nodeType == n.ELEMENT_NODE]
|
||||
unknown_tags = set([n.tagName for n in nodes if n.tagName not in known.keys()])
|
||||
if unknown_tags:
|
||||
errors.append('The manifest of package "%s" (with format version %d) must not contain the following tags: %s' % (pkg.name, pkg.package_format, ', '.join(unknown_tags)))
|
||||
if 'run_depend' in unknown_tags and pkg.package_format >= 2:
|
||||
errors.append('Please replace <run_depend> tags with <exec_depend> tags.')
|
||||
elif 'exec_depend' in unknown_tags and pkg.package_format < 2:
|
||||
errors.append('Either update to a newer format or replace <exec_depend> tags with <run_depend> tags.')
|
||||
for node in [n for n in nodes if n.tagName in known.keys()]:
|
||||
errors += _check_known_attributes(node, known[node.tagName])
|
||||
if node.tagName not in ['description', 'export']:
|
||||
subnodes = [n for n in node.childNodes if n.nodeType == n.ELEMENT_NODE]
|
||||
if subnodes:
|
||||
errors.append('The "%s" tag must not contain the following children: %s' % (node.tagName, ', '.join([n.tagName for n in subnodes])))
|
||||
|
||||
if errors:
|
||||
raise InvalidPackage('Error(s):%s' % (''.join(['\n- %s' % e for e in errors])), filename)
|
||||
|
||||
pkg.validate(warnings=warnings)
|
||||
|
||||
return pkg
|
||||
|
||||
|
||||
def _get_nodes(parent, tagname):
|
||||
return [n for n in parent.childNodes if n.nodeType == n.ELEMENT_NODE and n.tagName == tagname]
|
||||
|
||||
|
||||
def _get_node(parent, tagname, filename):
|
||||
nodes = _get_nodes(parent, tagname)
|
||||
if len(nodes) != 1:
|
||||
raise InvalidPackage('The manifest must contain exactly one "%s" tag' % tagname, filename)
|
||||
return nodes[0]
|
||||
|
||||
|
||||
def _get_optional_node(parent, tagname, filename):
|
||||
nodes = _get_nodes(parent, tagname)
|
||||
if len(nodes) > 1:
|
||||
raise InvalidPackage('The manifest must not contain more than one "%s" tags' % tagname, filename)
|
||||
return nodes[0] if nodes else None
|
||||
|
||||
|
||||
def _get_node_value(node, allow_xml=False, apply_str=True):
|
||||
if allow_xml:
|
||||
value = (''.join([n.toxml() for n in node.childNodes])).strip(' \n\r\t')
|
||||
else:
|
||||
value = (''.join([n.data for n in node.childNodes if n.nodeType == n.TEXT_NODE])).strip(' \n\r\t')
|
||||
if apply_str:
|
||||
value = str(value)
|
||||
return value
|
||||
|
||||
|
||||
def _get_node_text(node, strip=True):
|
||||
value = ''
|
||||
for child in node.childNodes:
|
||||
if child.nodeType == child.TEXT_NODE:
|
||||
value += re.sub(r'\s+', ' ', child.data)
|
||||
elif child.nodeType == child.ELEMENT_NODE:
|
||||
if child.tagName == 'br':
|
||||
value += '\n'
|
||||
else:
|
||||
value += _get_node_text(child, strip=False)
|
||||
else:
|
||||
assert 'unreachable'
|
||||
if strip:
|
||||
value = value.strip()
|
||||
return value
|
||||
|
||||
|
||||
def _get_node_attr(node, attr, default=False):
|
||||
""":param default: False means value is required."""
|
||||
if node.hasAttribute(attr):
|
||||
return str(node.getAttribute(attr))
|
||||
if default is False:
|
||||
raise InvalidPackage('The "%s" tag must have the attribute "%s"' % (node.tagName, attr))
|
||||
return default
|
||||
|
||||
|
||||
def _get_dependencies(parent, tagname):
|
||||
depends = []
|
||||
for node in _get_nodes(parent, tagname):
|
||||
depend = Dependency(_get_node_value(node))
|
||||
for attr in ('version_lt', 'version_lte', 'version_eq', 'version_gte', 'version_gt', 'condition'):
|
||||
setattr(depend, attr, _get_node_attr(node, attr, None))
|
||||
depends.append(depend)
|
||||
return depends
|
||||
|
||||
|
||||
def _get_group_dependencies(parent, tagname):
|
||||
from .group_dependency import GroupDependency
|
||||
depends = []
|
||||
for node in _get_nodes(parent, tagname):
|
||||
depends.append(
|
||||
GroupDependency(
|
||||
_get_node_value(node),
|
||||
condition=_get_node_attr(node, 'condition', default=None)))
|
||||
return depends
|
||||
|
||||
|
||||
def _get_group_memberships(parent, tagname):
|
||||
from .group_membership import GroupMembership
|
||||
memberships = []
|
||||
for node in _get_nodes(parent, tagname):
|
||||
memberships.append(
|
||||
GroupMembership(
|
||||
_get_node_value(node),
|
||||
condition=_get_node_attr(node, 'condition', default=None)))
|
||||
return memberships
|
||||
|
|
@ -0,0 +1,445 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2012, Willow Garage, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Willow Garage, Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived
|
||||
# from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import getpass
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
|
||||
from catkin_pkg.cmake import configure_file
|
||||
from catkin_pkg.cmake import get_metapackage_cmake_template_path
|
||||
from catkin_pkg.package import Dependency
|
||||
from catkin_pkg.package import Package
|
||||
from catkin_pkg.package import PACKAGE_MANIFEST_FILENAME
|
||||
from catkin_pkg.package import Person
|
||||
|
||||
|
||||
class PackageTemplate(Package):
|
||||
|
||||
def __init__(self, catkin_deps=None, system_deps=None, boost_comps=None, **kwargs):
|
||||
super(PackageTemplate, self).__init__(**kwargs)
|
||||
self.catkin_deps = catkin_deps or []
|
||||
self.system_deps = system_deps or []
|
||||
self.boost_comps = boost_comps or []
|
||||
self.validate()
|
||||
|
||||
@staticmethod
|
||||
def _create_package_template(package_name, description=None, licenses=None,
|
||||
maintainer_names=None, author_names=None,
|
||||
version=None, catkin_deps=None, system_deps=None,
|
||||
boost_comps=None):
|
||||
"""
|
||||
Alternative factory method mapping CLI args to argument for Package class.
|
||||
|
||||
:param package_name:
|
||||
:param description:
|
||||
:param licenses:
|
||||
:param maintainer_names:
|
||||
:param authors:
|
||||
:param version:
|
||||
:param catkin_deps:
|
||||
"""
|
||||
# Sort so they are alphebetical
|
||||
licenses = list(licenses or ['TODO'])
|
||||
licenses.sort()
|
||||
if not maintainer_names:
|
||||
maintainer_names = [getpass.getuser()]
|
||||
maintainer_names = list(maintainer_names or [])
|
||||
maintainer_names.sort()
|
||||
maintainers = []
|
||||
for maintainer_name in maintainer_names:
|
||||
maintainers.append(
|
||||
Person(maintainer_name,
|
||||
'%s@todo.todo' % maintainer_name.split()[-1])
|
||||
)
|
||||
author_names = list(author_names or [])
|
||||
author_names.sort()
|
||||
authors = []
|
||||
for author_name in author_names:
|
||||
authors.append(Person(author_name))
|
||||
catkin_deps = list(catkin_deps or [])
|
||||
catkin_deps.sort()
|
||||
pkg_catkin_deps = []
|
||||
depends = []
|
||||
build_depends = []
|
||||
exec_depends = []
|
||||
buildtool_depends = [Dependency('catkin')]
|
||||
for dep in catkin_deps:
|
||||
if dep.lower() == 'catkin':
|
||||
catkin_deps.remove(dep)
|
||||
continue
|
||||
if dep.lower() == 'genmsg':
|
||||
sys.stderr.write('WARNING: Packages with messages or services should not depend on genmsg, but on message_generation and message_runtime\n')
|
||||
buildtool_depends.append(Dependency('genmsg'))
|
||||
continue
|
||||
if dep.lower() == 'message_generation':
|
||||
if 'message_runtime' not in catkin_deps:
|
||||
sys.stderr.write('WARNING: Packages with messages or services should depend on both message_generation and message_runtime\n')
|
||||
build_depends.append(Dependency('message_generation'))
|
||||
continue
|
||||
if dep.lower() == 'message_runtime':
|
||||
if 'message_generation' not in catkin_deps:
|
||||
sys.stderr.write('WARNING: Packages with messages or services should depend on both message_generation and message_runtime\n')
|
||||
exec_depends.append(Dependency('message_runtime'))
|
||||
continue
|
||||
pkg_catkin_deps.append(Dependency(dep))
|
||||
for dep in pkg_catkin_deps:
|
||||
depends.append(dep)
|
||||
if boost_comps:
|
||||
if not system_deps:
|
||||
system_deps = ['boost']
|
||||
elif 'boost' not in system_deps:
|
||||
system_deps.append('boost')
|
||||
for dep in system_deps or []:
|
||||
if not dep.lower().startswith('python-'):
|
||||
depends.append(Dependency(dep))
|
||||
else:
|
||||
exec_depends.append(Dependency(dep))
|
||||
package_temp = PackageTemplate(
|
||||
name=package_name,
|
||||
version=version or '0.0.0',
|
||||
description=description or 'The %s package' % package_name,
|
||||
buildtool_depends=buildtool_depends,
|
||||
build_depends=build_depends,
|
||||
depends=depends,
|
||||
exec_depends=exec_depends,
|
||||
catkin_deps=catkin_deps,
|
||||
system_deps=system_deps,
|
||||
boost_comps=boost_comps,
|
||||
licenses=licenses,
|
||||
authors=authors,
|
||||
maintainers=maintainers,
|
||||
urls=[])
|
||||
return package_temp
|
||||
|
||||
|
||||
def read_template_file(filename, rosdistro):
|
||||
template_dir = os.path.join(os.path.dirname(__file__), 'templates')
|
||||
templates = []
|
||||
templates.append(os.path.join(template_dir, rosdistro, '%s.in' % filename))
|
||||
templates.append(os.path.join(template_dir, '%s.in' % filename))
|
||||
for template in templates:
|
||||
if os.path.isfile(template):
|
||||
with open(template, 'r') as fhand:
|
||||
template_contents = fhand.read()
|
||||
return template_contents
|
||||
raise IOError(
|
||||
'Could not read template for ROS distro '
|
||||
"'{}' at '{}': ".format(rosdistro, ', '.join(templates)) +
|
||||
'no such file or directory'
|
||||
)
|
||||
|
||||
|
||||
def _safe_write_files(newfiles, target_dir):
|
||||
"""
|
||||
Write file contents to target_dir/filepath for all entries of newfiles.
|
||||
|
||||
Aborts early if files exist in places for new files or directories
|
||||
|
||||
:param newfiles: a dict {filepath: contents}
|
||||
:param target_dir: a string
|
||||
"""
|
||||
# first check no filename conflict exists
|
||||
for filename in newfiles:
|
||||
target_file = os.path.join(target_dir, filename)
|
||||
if os.path.exists(target_file):
|
||||
raise ValueError('File exists: %s' % target_file)
|
||||
dirname = os.path.dirname(target_file)
|
||||
while dirname != target_dir:
|
||||
if os.path.isfile(dirname):
|
||||
raise ValueError('Cannot create directory, file exists: %s' %
|
||||
dirname)
|
||||
dirname = os.path.dirname(dirname)
|
||||
|
||||
for filename, content in newfiles.items():
|
||||
target_file = os.path.join(target_dir, filename)
|
||||
dirname = os.path.dirname(target_file)
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
# print(target_file, content)
|
||||
with open(target_file, 'ab') as fhand:
|
||||
fhand.write(content.encode())
|
||||
print('Created file %s' % os.path.relpath(target_file, os.path.dirname(target_dir)))
|
||||
|
||||
|
||||
def create_package_files(target_path, package_template, rosdistro,
|
||||
newfiles=None, meta=False):
|
||||
"""
|
||||
Create several files from templates to start a new package.
|
||||
|
||||
:param target_path: parent folder where to create the package
|
||||
:param package_template: contains the required information
|
||||
:param rosdistro: name of the distro to look up respective template
|
||||
:param newfiles: dict {filepath: contents} for additional files to write
|
||||
"""
|
||||
if newfiles is None:
|
||||
newfiles = {}
|
||||
# allow to replace default templates when path string is equal
|
||||
manifest_path = os.path.join(target_path, PACKAGE_MANIFEST_FILENAME)
|
||||
if manifest_path not in newfiles:
|
||||
newfiles[manifest_path] = \
|
||||
create_package_xml(package_template, rosdistro, meta=meta)
|
||||
cmake_path = os.path.join(target_path, 'CMakeLists.txt')
|
||||
if cmake_path not in newfiles:
|
||||
newfiles[cmake_path] = create_cmakelists(package_template, rosdistro, meta=meta)
|
||||
_safe_write_files(newfiles, target_path)
|
||||
if 'roscpp' in package_template.catkin_deps:
|
||||
fname = os.path.join(target_path, 'include', package_template.name)
|
||||
os.makedirs(fname)
|
||||
print('Created folder %s' % os.path.relpath(fname, os.path.dirname(target_path)))
|
||||
if 'roscpp' in package_template.catkin_deps or \
|
||||
'rospy' in package_template.catkin_deps:
|
||||
fname = os.path.join(target_path, 'src')
|
||||
os.makedirs(fname)
|
||||
print('Created folder %s' % os.path.relpath(fname, os.path.dirname(target_path)))
|
||||
|
||||
|
||||
class CatkinTemplate(string.Template):
|
||||
"""subclass to use @ instead of $ as markers."""
|
||||
|
||||
delimiter = '@'
|
||||
escape = '@'
|
||||
|
||||
|
||||
def create_cmakelists(package_template, rosdistro, meta=False):
|
||||
"""Create CMake file contents from the template.
|
||||
|
||||
:param package_template: contains the required information
|
||||
:returns: file contents as string
|
||||
"""
|
||||
if meta:
|
||||
template_path = get_metapackage_cmake_template_path()
|
||||
temp_dict = {
|
||||
'name': package_template.name,
|
||||
'metapackage_arguments': '',
|
||||
}
|
||||
return configure_file(template_path, temp_dict)
|
||||
else:
|
||||
cmakelists_txt_template = read_template_file('CMakeLists.txt', rosdistro)
|
||||
ctemp = CatkinTemplate(cmakelists_txt_template)
|
||||
if package_template.catkin_deps == []:
|
||||
components = ''
|
||||
else:
|
||||
components = ' COMPONENTS\n %s\n' % '\n '.join(package_template.catkin_deps)
|
||||
boost_find_package = \
|
||||
('' if not package_template.boost_comps
|
||||
else ('find_package(Boost REQUIRED COMPONENTS %s)\n' %
|
||||
' '.join(package_template.boost_comps)))
|
||||
system_find_package = ''
|
||||
for sysdep in package_template.system_deps:
|
||||
if sysdep == 'boost':
|
||||
continue
|
||||
if sysdep.startswith('python-'):
|
||||
system_find_package += '# '
|
||||
system_find_package += 'find_package(%s REQUIRED)\n' % sysdep
|
||||
# provide dummy values
|
||||
catkin_depends = (' '.join(package_template.catkin_deps)
|
||||
if package_template.catkin_deps
|
||||
else 'other_catkin_pkg')
|
||||
system_depends = (' '.join(package_template.system_deps)
|
||||
if package_template.system_deps
|
||||
else 'system_lib')
|
||||
message_pkgs = [pkg for pkg in package_template.catkin_deps if pkg.endswith('_msgs')]
|
||||
if message_pkgs:
|
||||
message_depends = '# %s' % '# '.join(message_pkgs)
|
||||
else:
|
||||
message_depends = '# std_msgs # Or other packages containing msgs'
|
||||
temp_dict = {'name': package_template.name,
|
||||
'components': components,
|
||||
'include_directories': _create_include_macro(package_template),
|
||||
'boost_find': boost_find_package,
|
||||
'systems_find': system_find_package,
|
||||
'catkin_depends': catkin_depends,
|
||||
'system_depends': system_depends,
|
||||
'target_libraries': _create_targetlib_args(package_template),
|
||||
'message_dependencies': message_depends
|
||||
}
|
||||
return ctemp.substitute(temp_dict)
|
||||
|
||||
|
||||
def _create_targetlib_args(package_template):
|
||||
result = '# ${catkin_LIBRARIES}\n'
|
||||
if package_template.boost_comps:
|
||||
result += '# ${Boost_LIBRARIES}\n'
|
||||
if package_template.system_deps:
|
||||
result += (''.join(['# ${%s_LIBRARIES}\n' %
|
||||
sdep for sdep in package_template.system_deps]))
|
||||
return result
|
||||
|
||||
|
||||
def _create_include_macro(package_template):
|
||||
includes = ['# include']
|
||||
includes.append((' ' if package_template.catkin_deps else '# ') + '${catkin_INCLUDE_DIRS}')
|
||||
if package_template.boost_comps:
|
||||
includes.append(' ${Boost_INCLUDE_DIRS}')
|
||||
if package_template.system_deps:
|
||||
deplist = []
|
||||
for sysdep in package_template.system_deps:
|
||||
if not sysdep.startswith('python-'):
|
||||
deplist.append(sysdep)
|
||||
if deplist:
|
||||
todo_incl = '# TODO: Check names of system library include directories'
|
||||
includes.append(todo_incl + (' (%s)' % ', '.join(deplist)))
|
||||
includes.extend([' ${%s_INCLUDE_DIRS}' % sysdep for sysdep in deplist])
|
||||
result = ''
|
||||
if includes:
|
||||
result += '\n'.join(includes)
|
||||
return result
|
||||
|
||||
|
||||
def _create_depend_tag(dep_type,
|
||||
name,
|
||||
version_eq=None,
|
||||
version_lt=None,
|
||||
version_lte=None,
|
||||
version_gt=None,
|
||||
version_gte=None):
|
||||
"""Create xml snippet for package.xml."""
|
||||
version_string = []
|
||||
for key, var in {'version_eq': version_eq,
|
||||
'version_lt': version_lt,
|
||||
'version_lte': version_lte,
|
||||
'version_gt': version_gt,
|
||||
'version_gte': version_gte}.items():
|
||||
if var is not None:
|
||||
version_string.append(' %s="%s"' % (key, var))
|
||||
result = ' <%s%s>%s</%s>\n' % (dep_type,
|
||||
''.join(version_string),
|
||||
name,
|
||||
dep_type)
|
||||
return result
|
||||
|
||||
|
||||
def create_package_xml(package_template, rosdistro, meta=False):
|
||||
"""
|
||||
Create package xml file content.
|
||||
|
||||
:param package_template: contains the required information
|
||||
:returns: file contents as string
|
||||
"""
|
||||
package_xml_template = \
|
||||
read_template_file(PACKAGE_MANIFEST_FILENAME, rosdistro)
|
||||
ctemp = CatkinTemplate(package_xml_template)
|
||||
temp_dict = {}
|
||||
for key in package_template.__slots__:
|
||||
temp_dict[key] = getattr(package_template, key)
|
||||
|
||||
if package_template.version_compatibility:
|
||||
temp_dict['version_compatibility'] = \
|
||||
' compatibility="%s"' % package_template.version_compatibility
|
||||
else:
|
||||
temp_dict['version_compatibility'] = ''
|
||||
|
||||
if not package_template.description:
|
||||
temp_dict['description'] = 'The %s package ...' % package_template.name
|
||||
|
||||
licenses = []
|
||||
for plicense in package_template.licenses:
|
||||
licenses.append(' <license>%s</license>\n' % plicense)
|
||||
temp_dict['licenses'] = ''.join(licenses)
|
||||
|
||||
def get_person_tag(tagname, person):
|
||||
email_string = (
|
||||
'' if person.email is None else 'email="%s"' % person.email
|
||||
)
|
||||
return ' <%s %s>%s</%s>\n' % (tagname, email_string,
|
||||
person.name, tagname)
|
||||
|
||||
maintainers = []
|
||||
for maintainer in package_template.maintainers:
|
||||
maintainers.append(get_person_tag('maintainer', maintainer))
|
||||
temp_dict['maintainers'] = ''.join(maintainers)
|
||||
|
||||
urls = []
|
||||
for url in package_template.urls:
|
||||
type_string = ('' if url.type is None
|
||||
else 'type="%s"' % url.type)
|
||||
urls.append(' <url %s >%s</url>\n' % (type_string, url.url))
|
||||
temp_dict['urls'] = ''.join(urls)
|
||||
|
||||
authors = []
|
||||
for author in package_template.authors:
|
||||
authors.append(get_person_tag('author', author))
|
||||
temp_dict['authors'] = ''.join(authors)
|
||||
|
||||
dependencies = []
|
||||
dep_map = {
|
||||
'build_depend': package_template.build_depends,
|
||||
'build_export_depend': package_template.build_export_depends,
|
||||
'buildtool_depend': package_template.buildtool_depends,
|
||||
'exec_depend': package_template.exec_depends,
|
||||
'test_depend': package_template.test_depends,
|
||||
'conflict': package_template.conflicts,
|
||||
'replace': package_template.replaces
|
||||
}
|
||||
for dep_type in ['buildtool_depend', 'build_depend', 'build_export_depend',
|
||||
'exec_depend', 'test_depend', 'conflict', 'replace']:
|
||||
for dep in sorted(dep_map[dep_type], key=lambda x: x.name):
|
||||
if 'depend' in dep_type:
|
||||
dep_tag = _create_depend_tag(
|
||||
dep_type,
|
||||
dep.name,
|
||||
dep.version_eq,
|
||||
dep.version_lt,
|
||||
dep.version_lte,
|
||||
dep.version_gt,
|
||||
dep.version_gte
|
||||
)
|
||||
dependencies.append(dep_tag)
|
||||
else:
|
||||
dependencies.append(_create_depend_tag(dep_type,
|
||||
dep.name))
|
||||
temp_dict['dependencies'] = ''.join(dependencies)
|
||||
|
||||
exports = []
|
||||
if package_template.exports is not None:
|
||||
for export in package_template.exports:
|
||||
if export.content is not None:
|
||||
print('WARNING: Create package does not know how to '
|
||||
'serialize exports with content: '
|
||||
'%s, %s, ' % (export.tagname, export.attributes) +
|
||||
'%s' % (export.content),
|
||||
file=sys.stderr)
|
||||
else:
|
||||
attribs = [' %s="%s"' % (k, v) for (k, v) in export.attributes.items()]
|
||||
line = ' <%s%s/>\n' % (export.tagname, ''.join(attribs))
|
||||
exports.append(line)
|
||||
|
||||
if meta:
|
||||
exports.append(' <metapackage/>')
|
||||
temp_dict['exports'] = ''.join(exports)
|
||||
|
||||
temp_dict['components'] = package_template.catkin_deps
|
||||
|
||||
return ctemp.substitute(temp_dict)
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2012, Willow Garage, Inc.
|
||||
# Copyright (c) 2013, Open Source Robotics Foundation, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Open Source Robotics Foundation, Inc. nor
|
||||
# the names of its contributors may be used to endorse or promote
|
||||
# products derived from this software without specific prior
|
||||
# written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
|
||||
from catkin_pkg.changelog_generator import FORTHCOMING_LABEL
|
||||
import docutils.core
|
||||
|
||||
|
||||
def bump_version(version, bump='patch'):
|
||||
"""
|
||||
Increases version number.
|
||||
|
||||
:param str version: must be in version format "int.int.int"
|
||||
:param str bump: one of 'patch, minor, major'
|
||||
:returns: version with the given part increased, and all inferior parts reset to 0
|
||||
:rtype: str
|
||||
:raises ValueError: if the version string is not in the format x.y.z
|
||||
"""
|
||||
# split the version number
|
||||
match = re.match(r'^(\d+)\.(\d+)\.(\d+)$', version)
|
||||
if match is None:
|
||||
raise ValueError('Invalid version string, must be int.int.int: "%s"' % version)
|
||||
new_version = match.groups()
|
||||
new_version = [int(x) for x in new_version]
|
||||
# find the desired index
|
||||
idx = {'major': 0, 'minor': 1, 'patch': 2}[bump]
|
||||
# increment the desired part
|
||||
new_version[idx] += 1
|
||||
# reset all parts behind the bumped part
|
||||
new_version = new_version[:idx + 1] + [0 for x in new_version[idx + 1:]]
|
||||
return '%d.%d.%d' % tuple(new_version)
|
||||
|
||||
|
||||
def _replace_version(package_str, new_version):
|
||||
"""
|
||||
Replace the version tag in contents if there is only one instance.
|
||||
|
||||
:param str package_str: contents of package.xml
|
||||
:param str new_version: version number
|
||||
:returns: new package.xml
|
||||
:rtype: str
|
||||
:raises RuntimeError:
|
||||
"""
|
||||
# try to replace contens
|
||||
new_package_str, number_of_subs = re.subn('<version([^<>]*)>[^<>]*</version>', r'<version\g<1>>%s</version>' % new_version, package_str)
|
||||
if number_of_subs != 1:
|
||||
raise RuntimeError('Illegal number of version tags: %s' % (number_of_subs))
|
||||
return new_package_str
|
||||
|
||||
|
||||
def _replace_setup_py_version(setup_py_str, new_version):
|
||||
"""
|
||||
Replace the version tag in contents if there is only one instance and it is using a literal as the version.
|
||||
|
||||
:param str package_str: contents of setup.py
|
||||
:param str new_version: new version number
|
||||
:returns: new setup.py string
|
||||
:rtype: str
|
||||
:raises RuntimeError:
|
||||
"""
|
||||
# try to replace contents
|
||||
new_setup_py_str, number_of_subs = re.subn(
|
||||
r'version=([\'"])\d+\.\d+\.\d+([\'"]),',
|
||||
r'version=\g<1>%s\g<2>,' % new_version,
|
||||
setup_py_str)
|
||||
if number_of_subs == 0:
|
||||
raise RuntimeError("Failed to find a normal version statement, e.g.: version='1.2.3',")
|
||||
if number_of_subs != 1:
|
||||
raise RuntimeError('Illegal number of version statements: %s' % (number_of_subs))
|
||||
return new_setup_py_str
|
||||
|
||||
|
||||
def _check_for_version_comment(package_str, new_version):
|
||||
"""
|
||||
Check if a comment is present behind the version tag and return it.
|
||||
|
||||
:param str package_str: contents of package.xml
|
||||
:param str new_version: version number
|
||||
:returns: comment if available, else None
|
||||
:rtype: str
|
||||
"""
|
||||
version_tag = '>%s</version>' % new_version
|
||||
pattern = '%s[ \t]*%s *(.+) *%s' % (re.escape(version_tag), re.escape('<!--'), re.escape('-->'))
|
||||
comment = re.search(pattern, package_str)
|
||||
if comment:
|
||||
comment = comment.group(1)
|
||||
return comment
|
||||
|
||||
|
||||
def update_versions(packages, new_version):
|
||||
"""
|
||||
Bulk replace of version: searches for package.xml and setup.py files directly in given folders and replaces version tag within.
|
||||
|
||||
:param dict packages: dict from folder names to package xml objects in those folders
|
||||
:param str new_version: version string "int.int.int"
|
||||
:raises RuntimeError: if any one package.xml cannot be updated
|
||||
"""
|
||||
files = {}
|
||||
for path, package_obj in packages.items():
|
||||
# Update any package.xml files.
|
||||
package_path = os.path.join(path, 'package.xml')
|
||||
with open(package_path, 'r') as f:
|
||||
package_str = f.read()
|
||||
try:
|
||||
new_package_str = _replace_version(package_str, new_version)
|
||||
comment = _check_for_version_comment(new_package_str, new_version)
|
||||
if comment:
|
||||
print('NOTE: The package manifest "%s" contains a comment besides the version tag:\n %s' % (path, comment))
|
||||
except RuntimeError as rue:
|
||||
raise RuntimeError('Could not bump version number in file %s: %s' % (package_path, str(rue)))
|
||||
files[package_path] = new_package_str
|
||||
# Update any setup.py files.
|
||||
setup_py_path = os.path.join(path, 'setup.py')
|
||||
if os.path.exists(setup_py_path):
|
||||
# Only update setup.py for ament_python packages.
|
||||
build_types = package_obj.get_unconditional_build_types()
|
||||
if 'ament_python' in build_types:
|
||||
with open(setup_py_path, 'r') as f:
|
||||
setup_py_str = f.read()
|
||||
try:
|
||||
new_setup_py_str = _replace_setup_py_version(setup_py_str, new_version)
|
||||
except RuntimeError as exc:
|
||||
raise RuntimeError('Could not bump version number in file %s: %s' % (setup_py_path, str(exc)))
|
||||
files[setup_py_path] = new_setup_py_str
|
||||
|
||||
# if all replacements successful, write back modified package.xml
|
||||
for package_path, new_package_str in files.items():
|
||||
with open(package_path, 'w') as f:
|
||||
f.write(new_package_str)
|
||||
|
||||
|
||||
def get_forthcoming_label(rst):
|
||||
document = docutils.core.publish_doctree(rst)
|
||||
forthcoming_label = None
|
||||
for child in document.children:
|
||||
title = None
|
||||
if isinstance(child, docutils.nodes.subtitle):
|
||||
title = child
|
||||
elif isinstance(child, docutils.nodes.section):
|
||||
section = child
|
||||
if len(section.children) > 0 and isinstance(section.children[0], docutils.nodes.title):
|
||||
title = section.children[0]
|
||||
if title and len(title.children) > 0 and isinstance(title.children[0], docutils.nodes.Text):
|
||||
title_text = title.children[0].astext()
|
||||
if FORTHCOMING_LABEL.lower() in title_text.lower():
|
||||
if forthcoming_label:
|
||||
raise RuntimeError('Found multiple forthcoming sections')
|
||||
forthcoming_label = title_text
|
||||
return forthcoming_label
|
||||
|
||||
|
||||
def update_changelog_sections(changelogs, new_version):
|
||||
# rename forthcoming sections to new_version including current date
|
||||
new_changelog_data = {}
|
||||
new_label = '%s (%s)' % (new_version, datetime.date.today().isoformat())
|
||||
for pkg_name, (changelog_path, changelog, forthcoming_label) in changelogs.items():
|
||||
data = rename_section(changelog.rst, forthcoming_label, new_label)
|
||||
new_changelog_data[changelog_path] = data
|
||||
|
||||
for changelog_path, data in new_changelog_data.items():
|
||||
with open(changelog_path, 'wb') as f:
|
||||
f.write(data.encode('utf-8'))
|
||||
|
||||
|
||||
def rename_section(data, old_label, new_label):
|
||||
valid_section_characters = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
|
||||
|
||||
def replace_section(match):
|
||||
section_char = match.group(2)[0]
|
||||
return new_label + '\n' + section_char * len(new_label)
|
||||
pattern = '^(' + re.escape(old_label) + ')\r?\n([' + re.escape(valid_section_characters) + ']+)\r?$'
|
||||
data, count = re.subn(pattern, replace_section, data, flags=re.MULTILINE)
|
||||
if count == 0:
|
||||
raise RuntimeError('Could not find section')
|
||||
if count > 1:
|
||||
raise RuntimeError('Found multiple matching sections')
|
||||
return data
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2012, Willow Garage, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Willow Garage, Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived
|
||||
# from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Library to find packages in the filesystem."""
|
||||
|
||||
import multiprocessing
|
||||
import os
|
||||
|
||||
from .package import _get_package_xml
|
||||
from .package import PACKAGE_MANIFEST_FILENAME
|
||||
from .package import parse_package_string
|
||||
|
||||
|
||||
DEFAULT_IGNORE_MARKERS = {'AMENT_IGNORE', 'CATKIN_IGNORE', 'COLCON_IGNORE'}
|
||||
|
||||
|
||||
def find_package_paths(basepath, exclude_paths=None, exclude_subspaces=False, ignore_markers=DEFAULT_IGNORE_MARKERS):
|
||||
"""
|
||||
Crawls the filesystem to find package manifest files.
|
||||
|
||||
When a subfolder contains either of the files mentioned in ``ignore_markers`` it is ignored. By default, these are:
|
||||
- ``AMENT_IGNORE``
|
||||
- ``CATKIN_IGNORE``
|
||||
- ``COLCON_IGNORE``
|
||||
|
||||
:param basepath: The path to search in, ``str``
|
||||
:param exclude_paths: A list of paths which should not be searched, ``list``
|
||||
:param exclude_subspaces: The flag is subfolders containing a .catkin file should not be
|
||||
searched, ``bool``
|
||||
:param ignore_markers: Names of files that indicate that a folder should be ignored, ``set``
|
||||
:returns: A list of relative paths containing package manifest files ``list``
|
||||
"""
|
||||
paths = []
|
||||
real_exclude_paths = [os.path.realpath(p) for p in exclude_paths] if exclude_paths is not None else []
|
||||
for dirpath, dirnames, filenames in os.walk(basepath, followlinks=True):
|
||||
if set(dirnames + filenames) & ignore_markers or \
|
||||
os.path.realpath(dirpath) in real_exclude_paths or \
|
||||
(exclude_subspaces and '.catkin' in filenames):
|
||||
del dirnames[:]
|
||||
continue
|
||||
elif PACKAGE_MANIFEST_FILENAME in filenames:
|
||||
paths.append(os.path.relpath(dirpath, basepath))
|
||||
del dirnames[:]
|
||||
continue
|
||||
# filter out hidden directories in-place
|
||||
dirnames[:] = [d for d in dirnames if not d.startswith('.')]
|
||||
return paths
|
||||
|
||||
|
||||
def find_packages(basepath, exclude_paths=None, exclude_subspaces=False, warnings=None, ignore_markers=DEFAULT_IGNORE_MARKERS):
|
||||
"""
|
||||
Crawls the filesystem to find package manifest files and parses them.
|
||||
|
||||
:param basepath: The path to search in, ``str``
|
||||
:param exclude_paths: A list of paths which should not be searched, ``list``
|
||||
:param exclude_subspaces: The flag is subfolders containing a .catkin file should not be
|
||||
searched, ``bool``
|
||||
:param warnings: Print warnings if None or return them in the given list, ``bool``
|
||||
:param ignore_markers: Names of files that indicate that a folder should be ignored, ``set``
|
||||
:returns: A dict mapping relative paths to ``Package`` objects ``dict``
|
||||
:raises: :exc:RuntimeError` If multiple packages have the same name
|
||||
"""
|
||||
packages = find_packages_allowing_duplicates(basepath, exclude_paths=exclude_paths,
|
||||
exclude_subspaces=exclude_subspaces, warnings=warnings,
|
||||
ignore_markers=ignore_markers)
|
||||
package_paths_by_name = {}
|
||||
for path, package in packages.items():
|
||||
if package.name not in package_paths_by_name:
|
||||
package_paths_by_name[package.name] = set()
|
||||
package_paths_by_name[package.name].add(path)
|
||||
duplicates = dict([(name, paths) for name, paths in package_paths_by_name.items() if len(paths) > 1])
|
||||
if duplicates:
|
||||
duplicates = ['Multiple packages found with the same name "%s":%s' % (name, ''.join(['\n- %s' % path_ for path_ in sorted(duplicates[name])])) for name in sorted(duplicates.keys())]
|
||||
raise RuntimeError('\n'.join(duplicates))
|
||||
return packages
|
||||
|
||||
|
||||
class _PackageParser(object):
|
||||
|
||||
def __init__(self, capture_warnings):
|
||||
self.capture_warnings = capture_warnings
|
||||
|
||||
def __call__(self, xml_and_path_and_filename):
|
||||
xml, path, filename = xml_and_path_and_filename
|
||||
warnings = [] if self.capture_warnings else None
|
||||
parsed_package = parse_package_string(xml, filename=filename, warnings=warnings)
|
||||
return (path, parsed_package), warnings
|
||||
|
||||
|
||||
def find_packages_allowing_duplicates(basepath, exclude_paths=None, exclude_subspaces=False, warnings=None, ignore_markers=DEFAULT_IGNORE_MARKERS):
|
||||
"""
|
||||
Crawls the filesystem to find package manifest files and parses them.
|
||||
|
||||
:param basepath: The path to search in, ``str``
|
||||
:param exclude_paths: A list of paths which should not be searched, ``list``
|
||||
:param exclude_subspaces: The flag is subfolders containing a .catkin file should not be
|
||||
searched, ``bool``
|
||||
:param warnings: Print warnings if None or return them in the given list
|
||||
:param ignore_markers: Names of files that indicate that a folder should be ignored, ``set``
|
||||
:returns: A dict mapping relative paths to ``Package`` objects ``dict``
|
||||
"""
|
||||
package_paths = find_package_paths(basepath, exclude_paths=exclude_paths, exclude_subspaces=exclude_subspaces, ignore_markers=ignore_markers)
|
||||
|
||||
xmls = {}
|
||||
for path in package_paths:
|
||||
xmls[path] = _get_package_xml(os.path.join(basepath, path))
|
||||
|
||||
data = [(v[0], k, v[1]) for k, v in xmls.items()]
|
||||
|
||||
if not data:
|
||||
return {}
|
||||
|
||||
parallel = len(data) > 100
|
||||
if parallel:
|
||||
try:
|
||||
pool = multiprocessing.Pool()
|
||||
except OSError:
|
||||
# On chroot environment, multiprocessing is not available
|
||||
# https://stackoverflow.com/questions/6033599/oserror-38-errno-38-with-multiprocessing
|
||||
parallel = False
|
||||
|
||||
if not parallel:
|
||||
# use sequential loop
|
||||
parsed_packages = {}
|
||||
for xml, path, filename in data:
|
||||
parsed_package = parse_package_string(
|
||||
xml, filename=filename, warnings=warnings)
|
||||
parsed_packages[path] = parsed_package
|
||||
return parsed_packages
|
||||
|
||||
# use multiprocessing pool
|
||||
parser = _PackageParser(warnings is not None)
|
||||
try:
|
||||
path_parsed_packages, warnings_lists = zip(*pool.map(parser, data))
|
||||
finally:
|
||||
pool.close()
|
||||
pool.join()
|
||||
if parser.capture_warnings:
|
||||
map(warnings.extend, warnings_lists)
|
||||
return dict(path_parsed_packages)
|
||||
|
||||
|
||||
def verify_equal_package_versions(packages):
|
||||
"""
|
||||
Verify that all packages have the same version number.
|
||||
|
||||
:param packages: The list of ``Package`` objects, ``list``
|
||||
:returns: The version number
|
||||
:raises: :exc:RuntimeError` If the version is not equal in all packages
|
||||
"""
|
||||
version = None
|
||||
for package in packages:
|
||||
if version is None:
|
||||
version = package.version
|
||||
elif package.version != version:
|
||||
raise RuntimeError('Two packages have different version numbers (%s != %s):\n- %s\n- %s' % (package.version, version, package.filename, list(packages)[0].filename))
|
||||
return version
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2012, Willow Garage, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Willow Garage, Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived
|
||||
# from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Library for providing the relevant information from the package manifest for the Python setup.py file."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from .package import InvalidPackage, parse_package
|
||||
|
||||
|
||||
def generate_distutils_setup(package_xml_path=os.path.curdir, **kwargs):
|
||||
"""
|
||||
Extract the information relevant for distutils from the package manifest.
|
||||
|
||||
The following keys will be set:
|
||||
|
||||
The "name" and "version" are taken from the eponymous tags.
|
||||
|
||||
A single maintainer will set the keys "maintainer" and
|
||||
"maintainer_email" while multiple maintainers are merged into the
|
||||
"maintainer" fields (including their emails). Authors are handled
|
||||
likewise.
|
||||
|
||||
The first URL of type "website" (or without a type) is used for
|
||||
the "url" field.
|
||||
|
||||
The "description" is taken from the eponymous tag if it does not
|
||||
exceed 200 characters and has no newlines. If it does "description"
|
||||
contains the truncated text while "long_description" contains the
|
||||
complete.
|
||||
|
||||
All licenses are merged into the "license" field.
|
||||
|
||||
:param kwargs: All keyword arguments are passed through. The above
|
||||
mentioned keys are verified to be identical if passed as a
|
||||
keyword argument
|
||||
|
||||
:returns: return dict populated with parsed fields and passed
|
||||
keyword arguments
|
||||
:raises: :exc:`InvalidPackage`
|
||||
:raises: :exc:`IOError`
|
||||
"""
|
||||
package = parse_package(package_xml_path)
|
||||
|
||||
data = {}
|
||||
data['name'] = package.name
|
||||
data['version'] = package.version
|
||||
|
||||
# either set one author with one email or join all in a single field
|
||||
if len(package.authors) == 1 and package.authors[0].email is not None:
|
||||
data['author'] = package.authors[0].name
|
||||
data['author_email'] = package.authors[0].email
|
||||
else:
|
||||
data['author'] = ', '.join([('%s <%s>' % (a.name, a.email) if a.email is not None else a.name) for a in package.authors])
|
||||
|
||||
# either set one maintainer with one email or join all in a single field
|
||||
if len(package.maintainers) == 1:
|
||||
data['maintainer'] = package.maintainers[0].name
|
||||
data['maintainer_email'] = package.maintainers[0].email
|
||||
else:
|
||||
data['maintainer'] = ', '.join(['%s <%s>' % (m.name, m.email) for m in package.maintainers])
|
||||
|
||||
# either set the first URL with the type 'website' or the first URL of any type
|
||||
websites = [url.url for url in package.urls if url.type == 'website']
|
||||
if websites:
|
||||
data['url'] = websites[0]
|
||||
elif package.urls:
|
||||
data['url'] = package.urls[0].url
|
||||
|
||||
description = package.plaintext_description.splitlines()[0]
|
||||
if len(description) > 200:
|
||||
description = description[:197] + '...'
|
||||
|
||||
data['description'] = description
|
||||
if description != package.plaintext_description:
|
||||
data['long_description'] = package.plaintext_description
|
||||
|
||||
data['license'] = ', '.join(package.licenses)
|
||||
|
||||
# pass keyword arguments and verify equality if generated and passed in
|
||||
for k, v in kwargs.items():
|
||||
if k in data:
|
||||
if v != data[k]:
|
||||
raise InvalidPackage('The keyword argument "%s" does not match the information from package.xml: "%s" != "%s"' % (k, v, data[k]), package_xml_path)
|
||||
else:
|
||||
data[k] = v
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_global_bin_destination():
|
||||
return 'bin'
|
||||
|
||||
|
||||
def get_global_etc_destination():
|
||||
return 'etc'
|
||||
|
||||
|
||||
def get_global_include_destination():
|
||||
return 'include'
|
||||
|
||||
|
||||
def get_global_lib_destination():
|
||||
return 'lib'
|
||||
|
||||
|
||||
def get_global_libexec_destination():
|
||||
return 'lib'
|
||||
|
||||
|
||||
def get_global_python_destination():
|
||||
dest = 'lib/python%u.%u/' % (sys.version_info[0], sys.version_info[1])
|
||||
if '--install-layout=deb' not in sys.argv[1:]:
|
||||
dest += 'site-packages'
|
||||
else:
|
||||
dest += 'dist-packages'
|
||||
return dest
|
||||
|
||||
|
||||
def get_global_share_destination():
|
||||
return 'share'
|
||||
|
||||
|
||||
def get_package_bin_destination(pkgname):
|
||||
return os.path.join(get_global_libexec_destination(), pkgname)
|
||||
|
||||
|
||||
def get_package_etc_destination(pkgname):
|
||||
return os.path.join(get_global_etc_destination(), pkgname)
|
||||
|
||||
|
||||
def get_package_include_destination(pkgname):
|
||||
return os.path.join(get_global_include_destination(), pkgname)
|
||||
|
||||
|
||||
def get_package_lib_destination(_pkgname):
|
||||
return get_global_lib_destination()
|
||||
|
||||
|
||||
def get_package_python_destination(pkgname):
|
||||
return os.path.join(get_global_python_destination(), pkgname)
|
||||
|
||||
|
||||
def get_package_share_destination(pkgname):
|
||||
return os.path.join(get_global_share_destination(), pkgname)
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2012, Willow Garage, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Willow Garage, Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived
|
||||
# from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""API provided for rospack to reorder include/library paths according to the chained workspaces."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from .workspaces import get_spaces, order_paths
|
||||
|
||||
|
||||
def reorder_paths(paths):
|
||||
paths_to_order = paths.split(' ') if paths else []
|
||||
ordered_paths = order_paths(paths_to_order, get_spaces())
|
||||
return ' '.join(ordered_paths)
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
cmake_minimum_required(VERSION 3.0.2)
|
||||
project(@name)
|
||||
|
||||
## Compile as C++11, supported in ROS Kinetic and newer
|
||||
# add_compile_options(-std=c++11)
|
||||
|
||||
## Find catkin macros and libraries
|
||||
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
|
||||
## is used, also find other catkin packages
|
||||
find_package(catkin REQUIRED@components)
|
||||
|
||||
## System dependencies are found with CMake's conventions
|
||||
# find_package(Boost REQUIRED COMPONENTS system)
|
||||
@boost_find@systems_find
|
||||
|
||||
## Uncomment this if the package has a setup.py. This macro ensures
|
||||
## modules and global scripts declared therein get installed
|
||||
## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
|
||||
# catkin_python_setup()
|
||||
|
||||
################################################
|
||||
## Declare ROS messages, services and actions ##
|
||||
################################################
|
||||
|
||||
## To declare and build messages, services or actions from within this
|
||||
## package, follow these steps:
|
||||
## * Let MSG_DEP_SET be the set of packages whose message types you use in
|
||||
## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...).
|
||||
## * In the file package.xml:
|
||||
## * add a build_depend tag for "message_generation"
|
||||
## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET
|
||||
## * If MSG_DEP_SET isn't empty the following dependency has been pulled in
|
||||
## but can be declared for certainty nonetheless:
|
||||
## * add a exec_depend tag for "message_runtime"
|
||||
## * In this file (CMakeLists.txt):
|
||||
## * add "message_generation" and every package in MSG_DEP_SET to
|
||||
## find_package(catkin REQUIRED COMPONENTS ...)
|
||||
## * add "message_runtime" and every package in MSG_DEP_SET to
|
||||
## catkin_package(CATKIN_DEPENDS ...)
|
||||
## * uncomment the add_*_files sections below as needed
|
||||
## and list every .msg/.srv/.action file to be processed
|
||||
## * uncomment the generate_messages entry below
|
||||
## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)
|
||||
|
||||
## Generate messages in the 'msg' folder
|
||||
# add_message_files(
|
||||
# FILES
|
||||
# Message1.msg
|
||||
# Message2.msg
|
||||
# )
|
||||
|
||||
## Generate services in the 'srv' folder
|
||||
# add_service_files(
|
||||
# FILES
|
||||
# Service1.srv
|
||||
# Service2.srv
|
||||
# )
|
||||
|
||||
## Generate actions in the 'action' folder
|
||||
# add_action_files(
|
||||
# FILES
|
||||
# Action1.action
|
||||
# Action2.action
|
||||
# )
|
||||
|
||||
## Generate added messages and services with any dependencies listed here
|
||||
# generate_messages(
|
||||
# DEPENDENCIES
|
||||
@message_dependencies
|
||||
# )
|
||||
|
||||
################################################
|
||||
## Declare ROS dynamic reconfigure parameters ##
|
||||
################################################
|
||||
|
||||
## To declare and build dynamic reconfigure parameters within this
|
||||
## package, follow these steps:
|
||||
## * In the file package.xml:
|
||||
## * add a build_depend and a exec_depend tag for "dynamic_reconfigure"
|
||||
## * In this file (CMakeLists.txt):
|
||||
## * add "dynamic_reconfigure" to
|
||||
## find_package(catkin REQUIRED COMPONENTS ...)
|
||||
## * uncomment the "generate_dynamic_reconfigure_options" section below
|
||||
## and list every .cfg file to be processed
|
||||
|
||||
## Generate dynamic reconfigure parameters in the 'cfg' folder
|
||||
# generate_dynamic_reconfigure_options(
|
||||
# cfg/DynReconf1.cfg
|
||||
# cfg/DynReconf2.cfg
|
||||
# )
|
||||
|
||||
###################################
|
||||
## catkin specific configuration ##
|
||||
###################################
|
||||
## The catkin_package macro generates cmake config files for your package
|
||||
## Declare things to be passed to dependent projects
|
||||
## INCLUDE_DIRS: uncomment this if your package contains header files
|
||||
## LIBRARIES: libraries you create in this project that dependent projects also need
|
||||
## CATKIN_DEPENDS: catkin_packages dependent projects also need
|
||||
## DEPENDS: system dependencies of this project that dependent projects also need
|
||||
catkin_package(
|
||||
# INCLUDE_DIRS include
|
||||
# LIBRARIES @{name}
|
||||
# CATKIN_DEPENDS @catkin_depends
|
||||
# DEPENDS @system_depends
|
||||
)
|
||||
|
||||
###########
|
||||
## Build ##
|
||||
###########
|
||||
|
||||
## Specify additional locations of header files
|
||||
## Your package locations should be listed before other locations
|
||||
include_directories(
|
||||
@include_directories
|
||||
)
|
||||
|
||||
## Declare a C++ library
|
||||
# add_library(${PROJECT_NAME}
|
||||
# src/${PROJECT_NAME}/@name.cpp
|
||||
# )
|
||||
|
||||
## Add cmake target dependencies of the library
|
||||
## as an example, code may need to be generated before libraries
|
||||
## either from message generation or dynamic reconfigure
|
||||
# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
|
||||
|
||||
## Declare a C++ executable
|
||||
## With catkin_make all packages are built within a single CMake context
|
||||
## The recommended prefix ensures that target names across packages don't collide
|
||||
# add_executable(${PROJECT_NAME}_node src/@{name}_node.cpp)
|
||||
|
||||
## Rename C++ executable without prefix
|
||||
## The above recommended prefix causes long target names, the following renames the
|
||||
## target back to the shorter version for ease of user use
|
||||
## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
|
||||
# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")
|
||||
|
||||
## Add cmake target dependencies of the executable
|
||||
## same as for the library above
|
||||
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
|
||||
|
||||
## Specify libraries to link a library or executable target against
|
||||
# target_link_libraries(${PROJECT_NAME}_node
|
||||
@target_libraries# )
|
||||
|
||||
#############
|
||||
## Install ##
|
||||
#############
|
||||
|
||||
# all install targets should use catkin DESTINATION variables
|
||||
# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html
|
||||
|
||||
## Mark executable scripts (Python etc.) for installation
|
||||
## in contrast to setup.py, you can choose the destination
|
||||
# catkin_install_python(PROGRAMS
|
||||
# scripts/my_python_script
|
||||
# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||
# )
|
||||
|
||||
## Mark executables for installation
|
||||
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html
|
||||
# install(TARGETS ${PROJECT_NAME}_node
|
||||
# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||
# )
|
||||
|
||||
## Mark libraries for installation
|
||||
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html
|
||||
# install(TARGETS ${PROJECT_NAME}
|
||||
# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||
# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||
# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
|
||||
# )
|
||||
|
||||
## Mark cpp header files for installation
|
||||
# install(DIRECTORY include/${PROJECT_NAME}/
|
||||
# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
|
||||
# FILES_MATCHING PATTERN "*.h"
|
||||
# PATTERN ".svn" EXCLUDE
|
||||
# )
|
||||
|
||||
## Mark other files for installation (e.g. launch and bag files, etc.)
|
||||
# install(FILES
|
||||
# # myfile1
|
||||
# # myfile2
|
||||
# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
|
||||
# )
|
||||
|
||||
#############
|
||||
## Testing ##
|
||||
#############
|
||||
|
||||
## Add gtest based cpp test target and link libraries
|
||||
# catkin_add_gtest(${PROJECT_NAME}-test test/test_@name.cpp)
|
||||
# if(TARGET ${PROJECT_NAME}-test)
|
||||
# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
|
||||
# endif()
|
||||
|
||||
## Add folders to be run by python nosetests
|
||||
# catkin_add_nosetests(test)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
cmake_minimum_required(VERSION 2.8.3)
|
||||
project(@name@)
|
||||
find_package(catkin REQUIRED)
|
||||
catkin_metapackage(@metapackage_arguments@)
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0"?>
|
||||
<package format="2">
|
||||
<name>@name</name>
|
||||
<version@version_compatibility>@version</version>
|
||||
<description>@description</description>
|
||||
|
||||
<!-- One maintainer tag required, multiple allowed, one person per tag -->
|
||||
<!-- Example: -->
|
||||
<!-- <maintainer email="jane.doe@@example.com">Jane Doe</maintainer> -->
|
||||
@maintainers
|
||||
|
||||
<!-- One license tag required, multiple allowed, one license per tag -->
|
||||
<!-- Commonly used license strings: -->
|
||||
<!-- BSD, MIT, Boost Software License, GPLv2, GPLv3, LGPLv2.1, LGPLv3 -->
|
||||
@licenses
|
||||
|
||||
<!-- Url tags are optional, but multiple are allowed, one per tag -->
|
||||
<!-- Optional attribute type can be: website, bugtracker, or repository -->
|
||||
<!-- Example: -->
|
||||
<!-- <url type="website">http://wiki.ros.org/@name</url> -->
|
||||
@urls
|
||||
|
||||
<!-- Author tags are optional, multiple are allowed, one per tag -->
|
||||
<!-- Authors do not have to be maintainers, but could be -->
|
||||
<!-- Example: -->
|
||||
<!-- <author email="jane.doe@@example.com">Jane Doe</author> -->
|
||||
@authors
|
||||
|
||||
<!-- The *depend tags are used to specify dependencies -->
|
||||
<!-- Dependencies can be catkin packages or system dependencies -->
|
||||
<!-- Examples: -->
|
||||
<!-- Use depend as a shortcut for packages that are both build and exec dependencies -->
|
||||
<!-- <depend>roscpp</depend> -->
|
||||
<!-- Note that this is equivalent to the following: -->
|
||||
<!-- <build_depend>roscpp</build_depend> -->
|
||||
<!-- <exec_depend>roscpp</exec_depend> -->
|
||||
<!-- Use build_depend for packages you need at compile time: -->
|
||||
<!-- <build_depend>message_generation</build_depend> -->
|
||||
<!-- Use build_export_depend for packages you need in order to build against this package: -->
|
||||
<!-- <build_export_depend>message_generation</build_export_depend> -->
|
||||
<!-- Use buildtool_depend for build tool packages: -->
|
||||
<!-- <buildtool_depend>catkin</buildtool_depend> -->
|
||||
<!-- Use exec_depend for packages you need at runtime: -->
|
||||
<!-- <exec_depend>message_runtime</exec_depend> -->
|
||||
<!-- Use test_depend for packages you need only for testing: -->
|
||||
<!-- <test_depend>gtest</test_depend> -->
|
||||
<!-- Use doc_depend for packages you need only for building documentation: -->
|
||||
<!-- <doc_depend>doxygen</doc_depend> -->
|
||||
@dependencies
|
||||
|
||||
<!-- The export tag contains other, unspecified, tags -->
|
||||
<export>
|
||||
<!-- Other tools can request additional information be placed here -->
|
||||
@exports
|
||||
</export>
|
||||
</package>
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2012, Willow Garage, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Willow Garage, Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived
|
||||
# from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Module to enable color terminal output."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import string
|
||||
|
||||
_ansi = {}
|
||||
|
||||
|
||||
def ansi(key):
|
||||
"""Return the escape sequence for a given ansi color key."""
|
||||
return _ansi[key]
|
||||
|
||||
|
||||
def enable_ANSI_colors():
|
||||
"""Populate the global module dictionary `ansi` with ANSI escape sequences."""
|
||||
color_order = [
|
||||
'black', 'red', 'green', 'yellow', 'blue', 'purple', 'cyan', 'white'
|
||||
]
|
||||
short_colors = {
|
||||
'black': 'k', 'red': 'r', 'green': 'g', 'yellow': 'y', 'blue': 'b',
|
||||
'purple': 'p', 'cyan': 'c', 'white': 'w'
|
||||
}
|
||||
# In python3.9+ syntax this could be `_ansi |= ...`
|
||||
_ansi.update({
|
||||
'escape': '\033', 'reset': 0, '|': 0,
|
||||
'boldon': 1, '!': 1, 'italicson': 3, '/': 3, 'ulon': 4, '_': 4,
|
||||
'invon': 7, 'boldoff': 22, 'italicsoff': 23,
|
||||
'uloff': 24, 'invoff': 27
|
||||
})
|
||||
|
||||
# Convert plain numbers to escapes
|
||||
for key in _ansi:
|
||||
if key != 'escape':
|
||||
_ansi[key] = '{0}[{1}m'.format(_ansi['escape'], _ansi[key])
|
||||
|
||||
# Foreground
|
||||
for index, color in enumerate(color_order):
|
||||
_ansi[color] = '{0}[{1}m'.format(_ansi['escape'], 30 + index)
|
||||
_ansi[color + 'f'] = _ansi[color]
|
||||
_ansi[short_colors[color] + 'f'] = _ansi[color + 'f']
|
||||
|
||||
# Background
|
||||
for index, color in enumerate(color_order):
|
||||
_ansi[color + 'b'] = '{0}[{1}m'.format(_ansi['escape'], 40 + index)
|
||||
_ansi[short_colors[color] + 'b'] = _ansi[color + 'b']
|
||||
|
||||
# Fmt sanitizers
|
||||
_ansi['atexclimation'] = '@!'
|
||||
_ansi['atfwdslash'] = '@/'
|
||||
_ansi['atunderscore'] = '@_'
|
||||
_ansi['atbar'] = '@|'
|
||||
|
||||
|
||||
def disable_ANSI_colors():
|
||||
"""Set all the ANSI escape sequences to empty strings, effectively disabling console colors."""
|
||||
for key in _ansi:
|
||||
_ansi[key] = ''
|
||||
|
||||
|
||||
# Default to ansi colors on
|
||||
enable_ANSI_colors()
|
||||
if os.name in ['nt']:
|
||||
disable_ANSI_colors()
|
||||
|
||||
|
||||
class ColorTemplate(string.Template):
|
||||
delimiter = '@'
|
||||
|
||||
|
||||
def sanitize(msg):
|
||||
"""Sanitize the existing msg, use before adding color annotations."""
|
||||
msg = msg.replace('@', '@@')
|
||||
msg = msg.replace('{', '{{')
|
||||
msg = msg.replace('}', '}}')
|
||||
msg = msg.replace('@@!', '@{atexclimation}')
|
||||
msg = msg.replace('@@/', '@{atfwdslash}')
|
||||
msg = msg.replace('@@_', '@{atunderscore}')
|
||||
msg = msg.replace('@@|', '@{atbar}')
|
||||
return msg
|
||||
|
||||
|
||||
def fmt(msg):
|
||||
"""Replace color annotations with ansi escape sequences."""
|
||||
msg = msg.replace('@!', '@{boldon}')
|
||||
msg = msg.replace('@/', '@{italicson}')
|
||||
msg = msg.replace('@_', '@{ulon}')
|
||||
msg = msg.replace('@|', '@{reset}')
|
||||
t = ColorTemplate(msg)
|
||||
return t.substitute(_ansi) + ansi('reset')
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2015, Open Source Robotics Foundation, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# * Neither the name of Willow Garage, Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived
|
||||
# from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
Common functions that can be used to mark spaces, e.g. build and devel, to indicate which tools previously built the space.
|
||||
|
||||
This allows the tools to detect cross tool talk and avoid it where appropriate
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
SPACE_BUILT_BY_MARKER_FILENAME = '.built_by'
|
||||
|
||||
|
||||
def get_previous_tool_used_on_the_space(space_path):
|
||||
"""
|
||||
Return the tool used to build the space at the given path, or None.
|
||||
|
||||
Returns None if the path does not exist or if there is no built by file.
|
||||
|
||||
:param str space_path: path to the space in question.
|
||||
:returns: str identifying the tool used to build the space or None.
|
||||
"""
|
||||
if os.path.isdir(space_path):
|
||||
marker_path = os.path.join(space_path, SPACE_BUILT_BY_MARKER_FILENAME)
|
||||
if os.path.isfile(marker_path):
|
||||
with open(marker_path, 'r') as f:
|
||||
return f.read().strip()
|
||||
return None
|
||||
|
||||
|
||||
def mark_space_as_built_by(space_path, tool_name):
|
||||
"""
|
||||
Place a marker file in the space at the given path, telling who built it.
|
||||
|
||||
The path to the marker is created if necessary.
|
||||
|
||||
:param str space_path: path to the space which should be marked.
|
||||
:param str tool_name: name of the tool doing the marking.
|
||||
:raises: OSError, others, when trying to create the folder.
|
||||
"""
|
||||
if not os.path.isdir(space_path):
|
||||
# Might fail if it's a file already or for permissions.
|
||||
os.makedirs(space_path)
|
||||
marker_path = os.path.join(space_path, SPACE_BUILT_BY_MARKER_FILENAME)
|
||||
with open(marker_path, 'w') as f:
|
||||
f.write(tool_name)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue