FreeCAD WebAssembly development snapshot
Some checks failed
FreeCAD master CI / Prepare (push) Has been cancelled
FreeCAD master CI / Pixi (push) Has been cancelled
FreeCAD master CI / Ubuntu (push) Has been cancelled
FreeCAD master CI / Windows (push) Has been cancelled
FreeCAD master CI / Lint (push) Has been cancelled
FreeCAD master CI / WrapUp (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Stale Issues / stale (push) Has been cancelled
Some checks failed
FreeCAD master CI / Prepare (push) Has been cancelled
FreeCAD master CI / Pixi (push) Has been cancelled
FreeCAD master CI / Ubuntu (push) Has been cancelled
FreeCAD master CI / Windows (push) Has been cancelled
FreeCAD master CI / Lint (push) Has been cancelled
FreeCAD master CI / WrapUp (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Stale Issues / stale (push) Has been cancelled
This commit is contained in:
11
tools/build/Docker/Dockerfile.Arch
Normal file
11
tools/build/Docker/Dockerfile.Arch
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM archlinux:base
|
||||
|
||||
COPY arch.sh /tmp
|
||||
|
||||
RUN pacman --sync --refresh --sysupgrade --noconfirm && \
|
||||
sh /tmp/arch.sh && \
|
||||
mkdir /builds
|
||||
|
||||
WORKDIR /builds
|
||||
|
||||
VOLUME [ "/builds" ]
|
||||
18
tools/build/Docker/Dockerfile.Conda
Normal file
18
tools/build/Docker/Dockerfile.Conda
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
&& \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# install Miniforge3
|
||||
RUN curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" && \
|
||||
bash Miniforge3-$(uname)-$(uname -m).sh -b -p /opt/miniforge3 && \
|
||||
rm "Miniforge3-$(uname)-$(uname -m).sh" && \
|
||||
/opt/miniforge3/bin/conda init
|
||||
|
||||
WORKDIR /builds
|
||||
|
||||
VOLUME [ "/builds" ]
|
||||
27
tools/build/Docker/Dockerfile.Debian
Normal file
27
tools/build/Docker/Dockerfile.Debian
Normal file
@@ -0,0 +1,27 @@
|
||||
FROM debian:12
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=Etc/UTC
|
||||
ENV UTC=true
|
||||
ENV ARC=false
|
||||
|
||||
COPY debian.sh /tmp
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade --yes && \
|
||||
sh /tmp/debian.sh && \
|
||||
mkdir /builds
|
||||
|
||||
# Debian 12 (bookworm) contains opencascade 7.6.x which contains deprecated constructs
|
||||
# (inheriting from std::iterator) which have been fixed in 7.7.x
|
||||
# lets bring the changes here to have less warnings during the build
|
||||
# https://github.com/Open-Cascade-SAS/OCCT/commit/8af9bbd59aecf24c765e7ea0eeeb8c9dd5c1f8db
|
||||
|
||||
RUN apt-get install curl --yes
|
||||
RUN curl --silent --location --output-dir /usr/include/opencascade/ \
|
||||
--remote-name https://github.com/Open-Cascade-SAS/OCCT/raw/8af9bbd59aecf24c765e7ea0eeeb8c9dd5c1f8db/src/NCollection/NCollection_StlIterator.hxx --next \
|
||||
--remote-name https://github.com/Open-Cascade-SAS/OCCT/raw/8af9bbd59aecf24c765e7ea0eeeb8c9dd5c1f8db/src/OSD/OSD_Parallel.hxx
|
||||
|
||||
WORKDIR /builds
|
||||
|
||||
VOLUME [ "/builds" ]
|
||||
11
tools/build/Docker/Dockerfile.Fedora
Normal file
11
tools/build/Docker/Dockerfile.Fedora
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM fedora:latest
|
||||
|
||||
COPY fedora.sh /tmp
|
||||
|
||||
RUN dnf update --assumeyes && \
|
||||
sh /tmp/fedora.sh && \
|
||||
mkdir /builds
|
||||
|
||||
WORKDIR /builds
|
||||
|
||||
VOLUME [ "/builds" ]
|
||||
11
tools/build/Docker/Dockerfile.Manjaro
Normal file
11
tools/build/Docker/Dockerfile.Manjaro
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM manjarolinux/base
|
||||
|
||||
COPY manjaro.sh /tmp
|
||||
|
||||
RUN pacman --sync --refresh --sysupgrade --noconfirm && \
|
||||
sh /tmp/manjaro.sh && \
|
||||
mkdir /builds
|
||||
|
||||
WORKDIR /builds
|
||||
|
||||
VOLUME [ "/builds" ]
|
||||
17
tools/build/Docker/Dockerfile.Ubuntu
Normal file
17
tools/build/Docker/Dockerfile.Ubuntu
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM ubuntu:rolling
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=Etc/UTC
|
||||
ENV UTC=true
|
||||
ENV ARC=false
|
||||
|
||||
COPY ubuntu.sh /tmp
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade --yes && \
|
||||
sh /tmp/ubuntu.sh && \
|
||||
mkdir -p /builds
|
||||
|
||||
WORKDIR /builds
|
||||
|
||||
VOLUME [ "/builds" ]
|
||||
17
tools/build/Docker/Dockerfile.UbuntuQt6
Normal file
17
tools/build/Docker/Dockerfile.UbuntuQt6
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM ubuntu:rolling
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=Etc/UTC
|
||||
ENV UTC=true
|
||||
ENV ARC=false
|
||||
|
||||
COPY ubuntuQt6.sh /tmp
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade --yes && \
|
||||
sh /tmp/ubuntuQt6.sh && \
|
||||
mkdir -p /builds
|
||||
|
||||
WORKDIR /builds
|
||||
|
||||
VOLUME [ "/builds" ]
|
||||
26
tools/build/Docker/README.conda.rst
Normal file
26
tools/build/Docker/README.conda.rst
Normal file
@@ -0,0 +1,26 @@
|
||||
========================
|
||||
FreeCAD build with Conda
|
||||
========================
|
||||
|
||||
Container
|
||||
=========
|
||||
|
||||
Build and run the `freecad-conda` container with the following commands:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
docker build --file tools/build/Docker/Dockerfile.Conda --tag freecad-conda .
|
||||
docker run --rm --interactive --tty --volume $(pwd):/builds:z freecad-conda
|
||||
|
||||
Build Code
|
||||
==========
|
||||
|
||||
Within the `freecad-conda` container, install the build dependencies and build
|
||||
FreeCAD using the following commands:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
conda/setup-environment.sh
|
||||
conda activate freecad
|
||||
cmake --preset conda-linux-debug
|
||||
cmake --build build/debug
|
||||
136
tools/build/Docker/README.rst
Normal file
136
tools/build/Docker/README.rst
Normal file
@@ -0,0 +1,136 @@
|
||||
==========================
|
||||
FreeCAD build dependencies
|
||||
==========================
|
||||
|
||||
Distributions
|
||||
=============
|
||||
|
||||
The following commands are used to install the packages needed to build FreeCAD from source on the following distributions.
|
||||
|
||||
|
||||
Arch Linux
|
||||
----------
|
||||
|
||||
The following command is used to install the required packages used to compile FreeCAD on Arch Linux.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
sudo sh tools/build/Docker/arch.sh
|
||||
|
||||
|
||||
Debian
|
||||
------
|
||||
|
||||
The following command is used to install the required packages used to compile FreeCAD on Debian.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
sudo sh tools/build/Docker/debian.sh
|
||||
|
||||
|
||||
Fedora
|
||||
------
|
||||
|
||||
The following command is used to install the required packages used to compile FreeCAD on Fedora.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
sudo sh tools/build/Docker/fedora.sh
|
||||
|
||||
|
||||
Manjaro
|
||||
-------
|
||||
|
||||
The following command is used to install the required packages used to compile FreeCAD on Manjaro Linux.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
sudo sh tools/build/Docker/manjaro.sh
|
||||
|
||||
|
||||
Ubuntu
|
||||
------
|
||||
|
||||
|
||||
The following command is used to install the required packages used to compile FreeCAD on Ubuntu Linux.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
sudo sh tools/build/Docker/ubuntu.sh
|
||||
|
||||
|
||||
Containers
|
||||
==========
|
||||
|
||||
The following will create containers that have all the required dependencies
|
||||
pre-installed that are needed to build FreeCAD from source.
|
||||
|
||||
|
||||
Arch Linux
|
||||
-----------
|
||||
|
||||
The following commands are used to create and run a Arch Linux build environment.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
docker build --file tools/build/Docker/Dockerfile.Arch --tag freecad-arch
|
||||
docker run --rm --interactive --tty --volume $(pwd):/builds:z freecad-arch
|
||||
|
||||
|
||||
Debian
|
||||
------
|
||||
|
||||
The following commands are used to create and run a Debian build environment.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
docker build --file tools/build/Docker/Dockerfile.Debian --tag freecad-debian
|
||||
docker run --rm --interactive --tty --volume $(pwd):/builds:z freecad-debian
|
||||
|
||||
|
||||
Fedora
|
||||
------
|
||||
|
||||
The following commands are used to create and run a Fedora build environment.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
docker build --file tools/build/Docker/Dockerfile.Fedora --tag freecad-fedora
|
||||
docker run --rm --interactive --tty --volume $(pwd):/builds:z freecad-fedora
|
||||
|
||||
|
||||
Manjaro
|
||||
-------
|
||||
|
||||
The following commands are used to create and run a Manjaro build environment.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
docker build --file tools/build/Docker/Dockerfile.Manjaro --tag freecad-manjaro
|
||||
docker run --rm --interactive --tty --volume $(pwd):/builds:z freecad-manjaro
|
||||
|
||||
|
||||
Ubuntu
|
||||
------
|
||||
|
||||
The following commands are used to create and run a Ubuntu build environment.
|
||||
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
docker build --file tools/build/Docker/Dockerfile.Ubuntu --tag freecad-ubuntu
|
||||
docker run --rm --interactive --tty --volume $(pwd):/builds:z freecad-ubuntu
|
||||
|
||||
|
||||
Build Code
|
||||
==========
|
||||
|
||||
To build the FreeCAD code inside one of the running containers the following
|
||||
commands should be used
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
mkdir freecad-build
|
||||
cd freecad-build
|
||||
cmake ../freecad-source
|
||||
make -j$(nproc --ignore=2)
|
||||
7
tools/build/Docker/arch.sh
Normal file
7
tools/build/Docker/arch.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
pacman --noconfirm --sync boost boost-libs cmake coin curl desktop-file-utils doxygen \
|
||||
eigen gc gcc-fortran git glew gnu-free-fonts guile hicolor-icon-theme jsoncpp \
|
||||
libspnav libtool make med opencascade openmpi pyside2 pyside2-tools \
|
||||
python-matplotlib python-netcdf4 python-pivy qt5-svg qt5-tools \
|
||||
shared-mime-info shiboken2 swig texinfo xerces-c
|
||||
|
||||
16
tools/build/Docker/debian.sh
Executable file
16
tools/build/Docker/debian.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
apt-get install --no-install-recommends --yes build-essential cmake doxygen \
|
||||
git libboost-date-time-dev libboost-dev libboost-filesystem-dev \
|
||||
libboost-graph-dev libboost-iostreams-dev libboost-program-options-dev \
|
||||
libboost-python-dev libboost-regex-dev libboost-serialization-dev \
|
||||
libboost-thread-dev libcoin-dev libeigen3-dev libgtkglext1-dev \
|
||||
libkdtree++-dev libkml-dev libmedc-dev libocct-data-exchange-dev \
|
||||
libocct-draw-dev libocct-foundation-dev libocct-modeling-algorithms-dev \
|
||||
libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev \
|
||||
libopencv-dev libproj-dev libpyside2-dev libqt5svg5-dev \
|
||||
libshiboken2-dev libvtk9-dev libvtk9-qt-dev \
|
||||
libvtk-dicom-dev libx11-dev libxerces-c-dev libxmu-dev libxmuu-dev \
|
||||
libzipios++-dev netgen netgen-headers pyside2-tools python3-dev \
|
||||
python3-matplotlib python3-pivy python3-ply python3-pyside2.qtsvg \
|
||||
python3-pyside2.qtuitools qtchooser qttools5-dev shiboken2 swig \
|
||||
python3-pyside2.qtnetwork libyaml-cpp-dev
|
||||
9
tools/build/Docker/fedora.sh
Normal file
9
tools/build/Docker/fedora.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
dnf --assumeyes install boost-devel cmake Coin3 Coin3-devel desktop-file-utils doxygen \
|
||||
eigen3-devel freeimage-devel freetype freetype-devel gcc gettext git libspnav-devel \
|
||||
libXmu-devel med med-devel mesa-libGLU-devel ode-devel opencascade-devel \
|
||||
opencv-devel openmpi-devel pcl-devel pyside2-tools python3 python3-devel \
|
||||
python3-matplotlib python3-pivy python3-pyside2-devel qt5-qtsvg-devel \
|
||||
qt5-qttools-static qt-devel \
|
||||
smesh-devel SoQt-devel swig tbb-devel vtk xerces-c xerces-c-devel
|
||||
|
||||
7
tools/build/Docker/manjaro.sh
Executable file
7
tools/build/Docker/manjaro.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
pacman --noconfirm --sync boost boost-libs cmake coin curl desktop-file-utils doxygen \
|
||||
eigen gc gcc-fortran git glew gnu-free-fonts guile hicolor-icon-theme jsoncpp \
|
||||
libspnav libtool make med opencascade pyside2 pyside2-tools python-matplotlib \
|
||||
python-netcdf4 python-pivy qt5-svg qt5-tools \
|
||||
shared-mime-info shiboken2 swig texinfo xerces-c
|
||||
|
||||
16
tools/build/Docker/ubuntu.sh
Normal file
16
tools/build/Docker/ubuntu.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
apt-get install --no-install-recommends --yes build-essential cmake doxygen git \
|
||||
libboost-date-time-dev libboost-dev libboost-filesystem-dev \
|
||||
libboost-graph-dev libboost-iostreams-dev libboost-program-options-dev \
|
||||
libboost-python-dev libboost-regex-dev libboost-serialization-dev \
|
||||
libboost-thread-dev libcoin-dev libeigen3-dev libgtkglext1-dev \
|
||||
libkdtree++-dev libkml-dev libmedc-dev libocct-data-exchange-dev \
|
||||
libocct-draw-dev libocct-foundation-dev libocct-modeling-algorithms-dev \
|
||||
libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev \
|
||||
libopencv-dev libproj-dev libpyside2-dev libqt5svg5-dev\
|
||||
libshiboken2-dev libvtk9-dev libvtk9-qt-dev \
|
||||
libvtk-dicom-dev libx11-dev libxerces-c-dev libxmu-dev libxmuu-dev \
|
||||
libyaml-cpp-dev libzipios++-dev netgen netgen-headers pyside2-tools python3-dev \
|
||||
python3-matplotlib python3-pivy python3-ply python3-pyside2.qtsvg \
|
||||
python3-pyside2.qtuitools qtchooser qttools5-dev shiboken2 swig \
|
||||
qtwayland5 python3-pyside2.qtnetwork python3-yaml
|
||||
20
tools/build/Docker/ubuntuQt6.sh
Normal file
20
tools/build/Docker/ubuntuQt6.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
apt-get install --no-install-recommends --yes build-essential cmake doxygen git \
|
||||
libtool lsb-release \
|
||||
python3 swig \
|
||||
libboost-dev libboost-date-time-dev libboost-filesystem-dev libboost-graph-dev libboost-iostreams-dev \
|
||||
libboost-program-options-dev libboost-python-dev libboost-regex-dev libboost-serialization-dev libboost-thread-dev \
|
||||
libcoin-dev \
|
||||
libeigen3-dev libgts-bin libgts-dev libkdtree++-dev libmedc-dev libopencv-dev libproj-dev libvtk9-dev \
|
||||
libx11-dev libxerces-c-dev libyaml-cpp-dev libzipios++-dev libpcl-dev python3-yaml \
|
||||
\
|
||||
pybind11-dev qt6-base-dev qt6-svg-dev qt6-tools-dev qt6-webengine-dev libpyside6-dev libshiboken6-dev pyside6-tools \
|
||||
pyqt6-dev-tools python3-dev python3-matplotlib python3-packaging python3-pivy python3-ply python3-pyside6.qtcore \
|
||||
python3-pyside6.qtgui python3-pyside6.qtnetwork python3-pyside6.qtsvg python3-pyside6.qtwebchannel \
|
||||
python3-pyside6.qtwebenginecore python3-pyside6.qtwebenginequick python3-pyside6.qtwebenginewidgets python3-pyside6.qtwidgets \
|
||||
\
|
||||
libocct-data-exchange-dev libocct-draw-dev libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev \
|
||||
libocct-ocaf-dev libocct-visualization-dev occt-draw \
|
||||
\
|
||||
libsimage-dev doxygen libcoin-doc libspnav-dev checkinstall
|
||||
299
tools/build/MacOS/BUILD_OSX.md
Normal file
299
tools/build/MacOS/BUILD_OSX.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# Contents
|
||||
|
||||
<a name="contents"></a>
|
||||
|
||||
- [Building FreeCAD on Mac OS 10.15.x -- Catalina](#build-freecad-macos-catalina)
|
||||
- [Directions](#directions)
|
||||
- [Install Xcode Command line tools](#install-xcode-cli-tools)
|
||||
- [Install Conda](#install-conda)
|
||||
- [Run the shell script](#run-the-shell-script)
|
||||
- [Building FreeCAD on macOS using homebrew packages with & without formula file](#homebrew-build-fc-on-macos)
|
||||
- [Requirements](#homebrew-requirements)
|
||||
- [Install required FreeCAD dependencies](#homebrew-install-required-deps)
|
||||
- [Limitations of using freecad formula file](#homebrew-limits-of-formula-file)
|
||||
- [Directions, Installing FreeCAD using brew packages without formula file](#homebrew-install-no-form-file)
|
||||
- [Expanded Directions](#homebrew-expanded-directions)
|
||||
- [Boost v1.75 fix](#homebrew-boost-175-fix)
|
||||
- [Errors, Issues, & Possible Solutions](#errors-issues-solutions)
|
||||
|
||||
<!-- SEE: https://stackoverflow.com/a/7335259/708807 for explanation of using `name` attribute for in page linking within a MD doc -->
|
||||
|
||||
# Building FreeCAD on Mac OS 10.15.x -- Catalina #
|
||||
|
||||
<a name="build-freecad-macos-catalina"></a>
|
||||
|
||||
General notes on how the tooling works:
|
||||
|
||||
This setup uses [conda](https://docs.conda.io) for dependency management.
|
||||
Conda is able to pull the deps from a repository called conda-forge and
|
||||
setup an isolated build environment. Not quite as isolated as docker, but
|
||||
it is a good option for Mac and is what the FreeCAD CI system uses.
|
||||
|
||||
Once the dependencies are installed into a conda environment, then the
|
||||
build uses the standard `cmake` configuration process to configure the build
|
||||
and `ninja` to invoke the actual compilation against whatever host compiler
|
||||
(gcc, clang, etc.) was installed by conda's `cxx-compiler` package for
|
||||
that architecture.
|
||||
|
||||
All of this, and some sanity checks, are in a unified shell script. See below.
|
||||
|
||||
# Directions
|
||||
|
||||
<a name="directions"></a>
|
||||
|
||||
## Install XCode Command line tools ##
|
||||
|
||||
<a name="install-xcode-cli-tools"></a>
|
||||
|
||||
Run `xcode-select --install` and click through.
|
||||
|
||||
## Install Conda ##
|
||||
|
||||
<a name="install-conda"></a>
|
||||
|
||||
Refer to [MiniConda Docs](https://docs.conda.io/en/latest/miniconda.html).
|
||||
|
||||
## Run the shell script ##
|
||||
|
||||
<a name="run-the-shell-script"></a>
|
||||
|
||||
Run the `./build_unix_dev_conda.sh` and go get coffee. Builds take
|
||||
an hour+ on a 2012 Retina MacBook.
|
||||
|
||||
Output binaries will be in the `./build/bin/FreeCAD` *and*
|
||||
`${CONDA_PREFIX}/bin/FreeCAD` directories.
|
||||
|
||||
You can code/build/test using the cmake configuration folder `./build` in
|
||||
the standard way *from within the freecad_dev conda environment*.
|
||||
|
||||
---
|
||||
|
||||
# Building FreeCAD on macOS using homebrew with & without a formula file
|
||||
|
||||
<a name="homebrew-build-fc-on-macos"></a>
|
||||
|
||||
> The below procedure provides an alternative way to install FreeCAD from the git source on macOS without having to use conda, but rather relies on [**mac homebrew**][lnk1] to manage dependencies.
|
||||
|
||||
## Requirements
|
||||
|
||||
<a name="homebrew-requirements"></a>
|
||||
|
||||
- macOS, running High Sierra _10.13_ or later
|
||||
- homebrew installed and working
|
||||
- All required dependencies to build FreeCAD installed using `brew install`
|
||||
|
||||
There is an official [**homebrew tap**][lnk2] that provides a list of formulas along with FreeCAD to setup all the dependencies to build FreeCAD from source on macOS, and also provides prebuilt bottles to install FreeCAD from a package rather than building from source.
|
||||
|
||||
> 💡 The below steps will build a FreeCAD binary that will launch FreeCAD from a command line interface, and will **NOT** build the **FreeCAD.app** bundle, ie. a double clickable app icon that can be launched from a Finder window.
|
||||
|
||||
### Install required FreeCAD dependencies
|
||||
|
||||
<a name="homebrew-install-required-deps"></a>
|
||||
|
||||
- Setup homebrew to use the official [**freecad-homebrew**][lnk2] tap.
|
||||
|
||||
```shell
|
||||
brew tap FreeCAD/freecad
|
||||
```
|
||||
|
||||
- Install FreeCAD dependencies provided by the tap
|
||||
|
||||
```shell
|
||||
brew install --only-dependencies freecad
|
||||
```
|
||||
|
||||
> The above step will install FreeCAD dependencies provided by the tap, and if a _bottle_ is provided by the tap homebrew will install the bottled version of the dep rather than building from source, unless the `install` command explicitly uses a _flag_ to build from source.
|
||||
|
||||
After all the dependencies have been installed, it should be possible to install FreeCAD from the provided bottle.
|
||||
|
||||
```shell
|
||||
brew install freecad/freecad/freecad
|
||||
```
|
||||
|
||||
> As of writing this, there are bottles provided for macOS Catalina and Big Sur
|
||||
> > If running a different version of macOS then building FreeCAD from source will be required.
|
||||
|
||||
To explicitly build FreeCAD from source using the formula file provided by the tap
|
||||
|
||||
```shell
|
||||
brew install freecad/freecad/freecad --build-from-source --HEAD --verbose
|
||||
```
|
||||
|
||||
The above command will grab the latest git source of FreeCAD and output the build process to the terminal.
|
||||
|
||||
> NOTE: On a MacBookPro 2013 late model it takes ~60 minutes to build FreeCAD from source.
|
||||
|
||||
After the _make_ and _make install_ process completes it should be possible to launch FreeCAD from any directory using a terminal with the below commands,
|
||||
|
||||
```shell
|
||||
FreeCAD
|
||||
FreeCADCmd
|
||||
```
|
||||
|
||||
- `FreeCAD` will launch a GUI version of FreeCAD
|
||||
- `FreeCADCmd` will launch **only** a command line version of FreeCAD
|
||||
|
||||
## Limitations of using the FreeCAD formula file
|
||||
|
||||
<a name="homebrew-limits-of-formula-file"></a>
|
||||
|
||||
If FreeCAD is installed via the bottle then one will have to wait for a new bottle to be generated to install a later version of FreeCAD. However, if FreeCAD is built from source, then FreeCAD will have all the updates up to the time the build process was started.
|
||||
|
||||
If any of the dependencies FreeCAD relies on is updated FreeCAD will likely require a rebuild. Mac homebrew does provide a feature to pin packages at specific versions to prevent them from updating, and also allows setting of an environment variable to prevent homebrew from automatically checking for updates (which can slow things down). All that said, FreeCAD can be built using all the dependencies provided by Mac homebrew, but not using the formula file: instead cloning the source to an arbitrary path on a local filesystem. This provides a couple of advantages:
|
||||
|
||||
- If `brew cleanup` is run and FreeCAD was installed using the above-provided command, all source tarballs or bottles that were _checked out_ or downloaded during the install process will be deleted from the system. If a reinstall or upgrade is later required then homebrew will have to refetch the bottles, or reclone the git source again.
|
||||
- Mac homebrew provides a method, _install flag_, for keeping the source regardless if the build succeeds or fails. The options are limited, however, and performing a standard `git clone` outside of homebrew is **much** preferred.
|
||||
- Cloning the FreeCAD source allows passing **any** cmake flags not provided by the formula file
|
||||
- Allowing the use of other build systems such as _ninja_
|
||||
- Allowing the use of alternate compilers, e.g. _ccache_
|
||||
- Pulling in subsequent updates are quicker because the `git clone` of the FreeCAD source will remain on the local filesystem even if a `brew cleanup` is run
|
||||
- Subsequent recompiles should not take 60 minutes if using a caching strategy such as _ccache_.
|
||||
|
||||
## Directions, Installing FreeCAD using brew packages without a formula file
|
||||
|
||||
<a name="homebrew-install-no-form-file"></a>
|
||||
|
||||
> ⚠️ The below directions assume macOS High Sierra or later is being used, homebrew is setup properly, and all dependencies were installed successfully.
|
||||
|
||||
**TL;DR**
|
||||
|
||||
- Clone the FreeCAD source, pass cmake args/flags within source dir, run make, and make install, then profit 💰
|
||||
|
||||
### Expanded Directions
|
||||
|
||||
<a name="homebrew-expanded-directions"></a>
|
||||
|
||||
- Clone the FreeCAD source from GitHub
|
||||
|
||||
```shell
|
||||
git clone https://github.com/freecad/freecad
|
||||
cd ./freecad
|
||||
git fetch
|
||||
```
|
||||
|
||||
> The above _fetch_ cmd will take some time to fetch the commit history for the repo, but if a shallow clone is performed then FreeCAD will not show to correct build number in the About dialog [**learn more**][lnk3].
|
||||
|
||||
Advanced users may alter the process below to build in a different location, use a different compiler, etc. but these instructions represent a procedure that works successfully for this author.
|
||||
|
||||
Set the path / environment variables for specifying the compilers to use
|
||||
|
||||
```
|
||||
export CC="/usr/local/opt/llvm/bin/clang"
|
||||
export CXX="/usr/local/opt/llvm/bin/clang++"
|
||||
```
|
||||
|
||||
- Linking the brew-provided install of python 3 will be required in order for cmake to find the proper python and python libraries.
|
||||
|
||||
```shell
|
||||
brew link python@3.9
|
||||
```
|
||||
|
||||
#### Boost v1.75 fix
|
||||
|
||||
<a name="homebrew-boost-175-fix"></a>
|
||||
|
||||
- Due to recent changes in boost v1.75, building FreeCAD will fail with the below linking error message (for a more exhaustive error message, [**learn more**][lnk4])
|
||||
|
||||
To work around the linking issue until the [**PR**][lnk5] is merged install boost will the patches applied within the PR.
|
||||
|
||||
```shell
|
||||
ld: library not found for -licudata
|
||||
```
|
||||
|
||||
```shell
|
||||
git checkout -b mybuild
|
||||
|
||||
cmake \
|
||||
-DCMAKE_C_FLAGS_RELEASE=-DNDEBUG \
|
||||
-DCMAKE_CXX_FLAGS_RELEASE=-DNDEBUG \
|
||||
-DCMAKE_INSTALL_PREFIX=/opt/beta/freecad \
|
||||
-DCMAKE_INSTALL_LIBDIR=lib \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_FIND_FRAMEWORK=LAST
|
||||
-DCMAKE_VERBOSE_MAKEFILE=ON \
|
||||
-Wno-dev \
|
||||
-DCMAKE_OSX_SYSROOT=/Library/Developer/CommandLineTools/SDKs/macOSX10.14.sdk \
|
||||
-std=c++14 \
|
||||
-DCMAKE_CXX_STANDARD=14 \
|
||||
-DBUILD_ENABLE_CXX_STD:STRING=C++14 \
|
||||
-Wno-deprecated-declarations \
|
||||
-DUSE_PYTHON3=1 -DPYTHON_EXECUTABLE=/usr/local/bin/python3 \
|
||||
-DBUILD_FEM_NETGEN=1 \
|
||||
-DBUILD_FEM=1 \
|
||||
-DBUILD_TECHDRAW=0 \
|
||||
-DFREECAD_USE_EXTERNAL_KDL=ON \
|
||||
-DFREECAD_CREATE_MAC_APP=OFF \
|
||||
-DCMAKE_PREFIX_PATH="/usr/local/opt/qt/lib/cmake;/usr/local/opt/nglib/Contents/Resources;/usr/local/opt/vtk@8.2/lib/cmake;/usr/local;" .
|
||||
```
|
||||
|
||||
After the configuration completes run the below commands to start the build & install process
|
||||
|
||||
```shell
|
||||
make
|
||||
make install
|
||||
```
|
||||
|
||||
> 💡 Author's note: The above cmake build flags are the ones I've had good luck with, but that's not to say other ones can be added or removed. And for reasons unknown to me the above build process takes ~ twice along than using `brew install --build-from-source`
|
||||
|
||||
If everything goes well FreeCAD should be able to launch from a terminal
|
||||
|
||||
## Errors and Issues + possible solutions
|
||||
|
||||
<a name="errors-issues-solutions"></a>
|
||||
|
||||
Some common pitfalls are listed in this section.
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary><strong>error:</strong> no member named </summary>
|
||||
|
||||
```shell
|
||||
[ 18%] Building CXX object src/Gui/CMakeFiles/FreeCADGui.dir/DlgProjectInformationImp.cpp.o
|
||||
cd /opt/code/github/public/forks/freecad/build/src/Gui && /usr/local/bin/ccache /usr/local/opt/llvm/bin/clang++ -DBOOST_ALL_NO_LIB -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PP_VARIADICS=1 -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_REGEX_DYN_LINK -DBOOST_SYSTEM_DYN_LINK -DBOOST_THREAD_DYN_LINK -DBUILD_ADDONMGR -DCMAKE_BUILD_TYPE=\"Release\" -DFreeCADGui_EXPORTS -DGL_SILENCE_DEPRECATION -DHAVE_CONFIG_H -DHAVE_FREEIMAGE -DHAVE_PYSIDE2 -DHAVE_RAPIDJSON -DHAVE_SHIBOKEN2 -DHAVE_TBB -DNDEBUG -DOCC_CONVERT_SIGNALS -DPYSIDE_QML_SUPPORT=1 -DQT_CORE_LIB -DQT_GUI_LIB -DQT_NETWORK_LIB -DQT_NO_DEBUG -DQT_OPENGL_LIB -DQT_PRINTSUPPORT_LIB -DQT_SVG_LIB -DQT_UITOOLS_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_OCC64 -I/opt/code/github/public/forks/freecad/build -I/opt/code/github/public/forks/freecad/build/src -I/opt/code/github/public/forks/freecad/src -I/opt/code/github/public/forks/freecad/src/Gui -I/opt/code/github/public/forks/freecad/src/Gui/Quarter -I/opt/code/github/public/forks/freecad/build/src/Gui -I/opt/code/github/public/forks/freecad/src/Gui/.. -I/opt/code/github/public/forks/freecad/build/src/Gui/.. -I/opt/code/github/public/forks/freecad/build/src/Gui/Language -I/opt/code/github/public/forks/freecad/build/src/Gui/propertyeditor -I/opt/code/github/public/forks/freecad/build/src/Gui/TaskView -I/opt/code/github/public/forks/freecad/build/src/Gui/Quarter -I/opt/code/github/public/forks/freecad/build/src/Gui/DAGView -I/usr/local/include/eigen3 -I/usr/local/include/PySide2/QtCore -I/usr/local/include/PySide2/QtGui -I/usr/local/include/PySide2/QtWidgets -isystem /usr/local/include -isystem /usr/local/Frameworks/Python.framework/Versions/3.9/include/python3.9 -iframework /usr/local/opt/qt/lib -isystem /usr/local/opt/qt/lib/QtCore.framework/Headers -isystem /usr/local/opt/qt/./mkspecs/macx-clang -isystem /usr/local/opt/qt/lib/QtWidgets.framework/Headers -isystem /usr/local/opt/qt/lib/QtGui.framework/Headers -isystem /Library/Developer/CommandLineTools/SDKs/macOSX10.14.sdk/System/Library/Frameworks/OpenGL.framework/Headers -isystem /usr/local/opt/qt/lib/QtOpenGL.framework/Headers -isystem /usr/local/opt/qt/lib/QtPrintSupport.framework/Headers -isystem /usr/local/opt/qt/lib/QtSvg.framework/Headers -isystem /usr/local/opt/qt/lib/QtNetwork.framework/Headers -isystem /usr/local/opt/qt/include -isystem /usr/local/opt/qt/include/QtUiTools -isystem /usr/local/include/shiboken2 -isystem /usr/local/include/PySide2 -isystem /usr/local/opt/qt/lib/QtXml.framework/Headers -Wall -Wextra -Wpedantic -Wno-write-strings -Wno-undefined-var-template -DNDEBUG -isysroot /Library/Developer/CommandLineTools/SDKs/macOSX10.14.sdk -fPIC -I/usr/local/Cellar/open-mpi/4.0.5/include -fPIC -std=gnu++14 -o CMakeFiles/FreeCADGui.dir/DlgProjectInformationImp.cpp.o -c /opt/code/github/public/forks/freecad/src/Gui/DlgProjectInformationImp.cpp
|
||||
/opt/code/github/public/forks/freecad/src/Gui/DlgProjectInformationImp.cpp:56:9: error: no member named 'lineEditProgramVersion' in 'Gui::Dialog::Ui_DlgProjectInformation'
|
||||
ui->lineEditProgramVersion->setText(QString::fromUtf8(doc->getProgramVersion()));
|
||||
~~ ^
|
||||
1 error generated.
|
||||
make[2]: *** [src/Gui/CMakeFiles/FreeCADGui.dir/DlgProjectInformationImp.cpp.o] Error 1
|
||||
make[1]: *** [src/Gui/CMakeFiles/FreeCADGui.dir/all] Error 2
|
||||
make: *** [all] Error 2
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
FreeCAD may fail to build if creating a build directory within the _src_ directory and running `cmake ..` within the newly created _build_ directory. As it currently stands, `cmake` needs run within the _src_ directory or the this error message will likely appear during the build process.
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary> ⚠️ <strong>warning:</strong> specified path differs in case from file name on disk</summary>
|
||||
|
||||
```
|
||||
/opt/code/github/public/forks/FreeCAD/src/Gui/moc_DlgParameterFind.cpp:10:10: warning: non-portable path to file '"../../../FreeCAD/src/Gui/DlgParameterFind.h"'; specified path differs in case from file name on disk [-Wnonportable-include-path]
|
||||
#include "../../../freecad/src/Gui/DlgParameterFind.h"
|
||||
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"../../../FreeCAD/src/Gui/DlgParameterFind.h"
|
||||
1 warning generated.
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
On macOS most filesystems are case **insensitive**, whereas most GNU+Linux distros use _case sensitive_ file systems. So if FreeCAD source is cloned within a `FreeCAD` directory the build process on macOS may look for a `freecad` that is _case sensitive_ however the file system isn't case sensitive, thus the compiler will provide the above warning message.
|
||||
|
||||
One way to resolve such error message is to rename `FreeCAD` to `freecad`
|
||||
|
||||
```shell
|
||||
mv FreeCAD freecadd;
|
||||
mv freecadd freecad;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<!-- links -->
|
||||
|
||||
[lnk1]: <http://brew.sh>
|
||||
[lnk2]: <https://github.com/FreeCAD/homebrew-freecad>
|
||||
[lnk3]: <https://forum.freecad.org/viewtopic.php?f=4&t=51981#p446796>
|
||||
[lnk4]: <https://gist.github.com/ipatch/6116824ab1f2a99b526cb07e43317b91#gistcomment-3577066>
|
||||
[lnk5]: <https://github.com/Homebrew/homebrew-core/pull/67615>
|
||||
103
tools/lint/black.py
Executable file
103
tools/lint/black.py
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from typing import Tuple
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
write_file,
|
||||
add_common_arguments,
|
||||
)
|
||||
|
||||
DEFAULT_LINE_LENGTH_LIMIT = 100
|
||||
|
||||
|
||||
def parse_black_output(output: str) -> Tuple[int, int]:
|
||||
"""
|
||||
Parse Black output to determine how many files would be reformatted and how many would fail.
|
||||
|
||||
Returns:
|
||||
A tuple (black_reformats, black_fails).
|
||||
"""
|
||||
black_reformats = 0
|
||||
black_fails = 0
|
||||
|
||||
# Look for a pattern like: "X files would be reformatted"
|
||||
reformats_matches = re.findall(r"(\d+)(?=\s+fil.+ would be reformatted)", output)
|
||||
if reformats_matches:
|
||||
black_reformats = int(reformats_matches[0])
|
||||
|
||||
# Look for a pattern like: "Y files would fail to reformat"
|
||||
fails_matches = re.findall(r"(\d+)(?=\s+fil.+ would fail to reformat)", output)
|
||||
if fails_matches:
|
||||
black_fails = int(fails_matches[0])
|
||||
|
||||
return black_reformats, black_fails
|
||||
|
||||
|
||||
def generate_markdown_report(black_reformats: int, black_fails: int, log_file: str) -> str:
|
||||
"""Generate a Markdown report section based on Black results and log file."""
|
||||
report_lines = []
|
||||
if black_reformats > 0 or black_fails > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Black would reformat {black_reformats} file(s)"
|
||||
f" and {black_fails} file(s) would fail to reformat</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Black would reformat no file</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
with open(log_file, "r", encoding="utf-8") as f:
|
||||
report_lines.append(f.read())
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run Black in check mode on Python files and append a Markdown report."
|
||||
)
|
||||
add_common_arguments(parser)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
logging.info("Installing black (if needed)...")
|
||||
run_command(["pipx", "install", "-q", "black"], check=True)
|
||||
|
||||
cmd = [
|
||||
"black",
|
||||
"--line-length",
|
||||
str(DEFAULT_LINE_LENGTH_LIMIT),
|
||||
"--check",
|
||||
] + args.files.split()
|
||||
stdout, stderr, exit_code = run_command(cmd, check=False)
|
||||
output = stdout + "\n" + stderr
|
||||
|
||||
log_file = os.path.join(args.log_dir, "black.log")
|
||||
write_file(log_file, output)
|
||||
emit_problem_matchers(log_file, "blackWarning.json", "black-warning")
|
||||
|
||||
black_reformats, black_fails = parse_black_output(output)
|
||||
logging.info(
|
||||
"Found %s files would be reformatted and %s files would fail to reformat",
|
||||
black_reformats,
|
||||
black_fails,
|
||||
)
|
||||
|
||||
report = generate_markdown_report(black_reformats, black_fails, log_file)
|
||||
append_file(args.report_file, report)
|
||||
|
||||
sys.exit(0 if exit_code == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
169
tools/lint/changed_lines.py
Normal file
169
tools/lint/changed_lines.py
Normal file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Modified to generate output compatible with `clang-tidy`'s `--line-filter` option
|
||||
#
|
||||
# Based on https://github.com/hestonhoffman/changed-lines/blob/main/main.py
|
||||
#
|
||||
# Original License
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2023 Heston Hoffman
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
"""
|
||||
Uses GitHub API to grab patch data for a PR and calculate changed lines
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
|
||||
|
||||
class MissingPatchData(Exception):
|
||||
"""Raised when the patch data is missing"""
|
||||
|
||||
|
||||
def fetch_patch(args):
|
||||
"""Grabs the patch data from the GitHub API."""
|
||||
git_session = requests.Session()
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
}
|
||||
if args.token:
|
||||
headers["Authorization"] = f"Bearer {args.token}"
|
||||
|
||||
git_request = git_session.get(
|
||||
f"{args.api_url}/repos/{args.repo}/pulls/{args.pr}/files", headers=headers
|
||||
)
|
||||
return git_request.json()
|
||||
|
||||
|
||||
def parse_patch_file(entry):
|
||||
"""Parses the individual file changes within a patch"""
|
||||
line_array = []
|
||||
sublist = []
|
||||
|
||||
patch_array = re.split("\n", entry["patch"])
|
||||
# clean patch array
|
||||
patch_array = [i for i in patch_array if i]
|
||||
|
||||
for item in patch_array:
|
||||
# Grabs hunk annotation and strips out added lines
|
||||
if item.startswith("@@ -"):
|
||||
if sublist:
|
||||
line_array.append(sublist)
|
||||
sublist = [re.sub(r"\s@@(.*)", "", item.split("+")[1])]
|
||||
# We don't need removed lines ('-')
|
||||
elif not item.startswith("-") and not item == "\\ No newline at end of file":
|
||||
sublist.append(item)
|
||||
if sublist:
|
||||
line_array.append(sublist)
|
||||
return line_array
|
||||
|
||||
|
||||
def parse_patch_data(patch_data):
|
||||
"""Takes the patch data and returns a dictionary of files and the lines"""
|
||||
|
||||
final_dict = {}
|
||||
for entry in patch_data:
|
||||
# We don't need removed files
|
||||
if entry["status"] == "removed":
|
||||
continue
|
||||
|
||||
# We can only operate on files with additions and a patch key
|
||||
# Some really big files don't have a patch key because GitHub
|
||||
# returns a message in the PR that the file is too large to display
|
||||
if entry["additions"] != 0 and "patch" in entry:
|
||||
line_array = parse_patch_file(entry)
|
||||
final_dict[entry["filename"]] = line_array
|
||||
return final_dict
|
||||
|
||||
|
||||
def get_lines(line_dict):
|
||||
"""Takes the dictionary of files and lines and returns a dictionary of files and line numbers"""
|
||||
final_dict = {}
|
||||
for file_name, sublist in line_dict.items():
|
||||
line_array = []
|
||||
for array in sublist:
|
||||
line_number = 0
|
||||
if "," not in array[0]:
|
||||
line_number = int(array[0]) - 1
|
||||
else:
|
||||
line_number = int(array[0].split(",")[0]) - 1
|
||||
|
||||
start = -1
|
||||
end = -1
|
||||
for line in array:
|
||||
if line.startswith("+"):
|
||||
if start < 0:
|
||||
start = line_number
|
||||
end = line_number
|
||||
# line_array.append(line_number)
|
||||
line_number += 1
|
||||
line_array.append([start, end])
|
||||
|
||||
# Remove deleted/renamed files (which appear as empty arrays)
|
||||
if line_array:
|
||||
final_dict[file_name] = line_array
|
||||
return final_dict
|
||||
|
||||
|
||||
def main():
|
||||
"""main()"""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="changed_lines.py",
|
||||
description="Identifies the changed files and lines in a GitHub PR.",
|
||||
)
|
||||
parser.add_argument("--token")
|
||||
parser.add_argument("--api-url", default="https://api.github.com")
|
||||
parser.add_argument("--repo", default="FreeCAD/FreeCAD")
|
||||
parser.add_argument("--ref", required=True)
|
||||
parser.add_argument("--pr", required=True)
|
||||
parser.add_argument("--file-filter", default="")
|
||||
args = parser.parse_args()
|
||||
|
||||
data = fetch_patch(args)
|
||||
added_line_data = parse_patch_data(data)
|
||||
added_lines = get_lines(added_line_data)
|
||||
|
||||
if args.file_filter:
|
||||
args.file_filter = set(args.file_filter.replace(" ", "").split(","))
|
||||
|
||||
filename_list = []
|
||||
line_filter = []
|
||||
for filename, _ in added_lines.items():
|
||||
if (not args.file_filter) or (
|
||||
os.path.splitext(filename)[1] in args.file_filter
|
||||
):
|
||||
filename_list.append(filename)
|
||||
lines_modified = {}
|
||||
lines_modified["name"] = filename
|
||||
lines_modified["lines"] = added_lines[filename]
|
||||
line_filter.append(lines_modified)
|
||||
|
||||
print(f"{json.dumps(line_filter)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
83
tools/lint/clang_format.py
Normal file
83
tools/lint/clang_format.py
Normal file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
write_file,
|
||||
add_common_arguments,
|
||||
)
|
||||
|
||||
|
||||
def count_clang_format_errors(log_text: str) -> int:
|
||||
"""
|
||||
Count lines that indicate clang-format violations.
|
||||
The pattern looks for lines ending with "[-Wclang-format-violations]".
|
||||
"""
|
||||
pattern = r"\[-Wclang-format-violations\]$"
|
||||
matches = re.findall(pattern, log_text, re.MULTILINE)
|
||||
logging.debug("Matches found: %s", matches)
|
||||
return len(matches)
|
||||
|
||||
|
||||
def generate_markdown_report(clang_format_errors: int, output: str) -> str:
|
||||
"""Generate a Markdown report section for the clang-format output."""
|
||||
report_lines = []
|
||||
if clang_format_errors > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Clang-format would reformat"
|
||||
f" {clang_format_errors} file(s)</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Clang-format would reformat no file</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run clang-format in dry-run mode on C++ files and append a Markdown report."
|
||||
)
|
||||
add_common_arguments(parser)
|
||||
parser.add_argument(
|
||||
"--clang-style",
|
||||
required=True,
|
||||
help="Clang-format style (e.g., 'file' to use .clang-format or a specific style).",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
cmd = (
|
||||
f"clang-format --dry-run --ferror-limit=1 --verbose "
|
||||
f"--style={args.clang_style} {args.files}"
|
||||
)
|
||||
stdout, stderr, _ = run_command(cmd)
|
||||
output = stdout + "\n" + stderr
|
||||
|
||||
log_file = os.path.join(args.log_dir, "clang-format.log")
|
||||
write_file(log_file, output)
|
||||
emit_problem_matchers(log_file, "clang.json", "clang")
|
||||
|
||||
clang_format_errors = count_clang_format_errors(output)
|
||||
logging.info("Found %s clang-format errors", clang_format_errors)
|
||||
|
||||
report = generate_markdown_report(clang_format_errors, output)
|
||||
append_file(args.report_file, report)
|
||||
|
||||
sys.exit(0 if clang_format_errors == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
140
tools/lint/clang_tidy.py
Normal file
140
tools/lint/clang_tidy.py
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
add_common_arguments,
|
||||
emit_problem_matchers,
|
||||
write_file,
|
||||
append_file,
|
||||
)
|
||||
|
||||
|
||||
def generate_markdown_report(
|
||||
errors_count: int,
|
||||
warnings_count: int,
|
||||
notes_count: int,
|
||||
clang_tidy_output: str,
|
||||
enabled_checks_content: str,
|
||||
) -> str:
|
||||
"""Generate a Markdown report section for clang-tidy results and enabled checks."""
|
||||
report_lines = []
|
||||
if errors_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:fire: Clang-Tidy found :fire: {errors_count} errors, "
|
||||
f":warning: {warnings_count} warnings and :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
elif warnings_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:warning: Clang-Tidy found :warning: {warnings_count} "
|
||||
f"warnings and :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
elif notes_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Clang-Tidy found :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Clang-Tidy found no errors, warnings or notes</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(clang_tidy_output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
report_lines.append(
|
||||
"<details><summary>:information_source: Enabled checks</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(enabled_checks_content)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
def count_occurrences(pattern: str, text: str) -> int:
|
||||
"""Count all occurrences of a regex pattern in text using MULTILINE mode."""
|
||||
matches = re.findall(pattern, text, re.MULTILINE)
|
||||
logging.info("Pattern '%s' found %d matches", pattern, len(matches))
|
||||
return len(matches)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run clang-tidy on provided C++ files and append a Markdown report."
|
||||
)
|
||||
add_common_arguments(parser)
|
||||
parser.add_argument(
|
||||
"--clang-style",
|
||||
required=True,
|
||||
help="Clang-format style (e.g., 'file' to use .clang-format or a specific style).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--line-filter",
|
||||
required=False,
|
||||
help='Line-filter for clang-tidy (i.e. [{"name":"file1.cpp","lines":[[1,3],[5,7]]},...])',
|
||||
)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
build_dir = "build"
|
||||
|
||||
clang_tidy_base_cmd = [
|
||||
"clang-tidy",
|
||||
"--quiet",
|
||||
f"--format-style={args.clang_style}",
|
||||
"--export-fixes=clang-tidy.yaml",
|
||||
"-p",
|
||||
build_dir,
|
||||
]
|
||||
|
||||
enabled_cmd = clang_tidy_base_cmd + ["--explain-config"]
|
||||
enabled_stdout, enabled_stderr, _ = run_command(enabled_cmd)
|
||||
enabled_output = enabled_stdout + enabled_stderr
|
||||
enabled_checks_log = os.path.join(args.log_dir, "clang-tidy-enabled-checks.log")
|
||||
write_file(enabled_checks_log, enabled_output)
|
||||
|
||||
clang_cmd = clang_tidy_base_cmd
|
||||
if args.line_filter:
|
||||
clang_cmd = clang_cmd + [f"--line-filter={args.line_filter}"]
|
||||
clang_cmd = clang_cmd + args.files.split()
|
||||
print("clang_cmd = ", clang_cmd)
|
||||
clang_stdout, clang_stderr, _ = run_command(clang_cmd)
|
||||
clang_tidy_output = clang_stdout + clang_stderr
|
||||
|
||||
clang_tidy_log = os.path.join(args.log_dir, "clang-tidy.log")
|
||||
write_file(clang_tidy_log, clang_tidy_output)
|
||||
emit_problem_matchers(clang_tidy_log, "clang.json", "clang")
|
||||
|
||||
error_pattern = r"^.+:\d+:\d+: error: .+$"
|
||||
warning_pattern = r"^.+:\d+:\d+: warning: .+$"
|
||||
note_pattern = r"^.+:\d+:\d+: note: .+$"
|
||||
|
||||
errors_count = count_occurrences(error_pattern, clang_tidy_output)
|
||||
warnings_count = count_occurrences(warning_pattern, clang_tidy_output)
|
||||
notes_count = count_occurrences(note_pattern, clang_tidy_output)
|
||||
|
||||
logging.info(
|
||||
"Found %d errors, %d warnings, %d notes",
|
||||
errors_count,
|
||||
warnings_count,
|
||||
notes_count,
|
||||
)
|
||||
|
||||
report = generate_markdown_report(
|
||||
errors_count, warnings_count, notes_count, clang_tidy_output, enabled_output
|
||||
)
|
||||
append_file(args.report_file, report)
|
||||
|
||||
sys.exit(0 if errors_count == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
119
tools/lint/clazy.py
Normal file
119
tools/lint/clazy.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
write_file,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
)
|
||||
|
||||
|
||||
def generate_markdown_report(
|
||||
aggregated_output: str, errors_count: int, warnings_count: int, notes_count: int
|
||||
) -> str:
|
||||
"""Generate a Markdown report based on clazy output and issue counts."""
|
||||
report_lines = []
|
||||
if errors_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:fire: Clazy found :fire: {errors_count} errors, "
|
||||
f":warning: {warnings_count} warnings and :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
elif warnings_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:warning: Clazy found :warning: {warnings_count} warnings "
|
||||
f"and :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
elif notes_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Clazy found :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Clazy found no errors, warnings or notes</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append(
|
||||
"[List of checks](https://github.com/KDE/clazy#list-of-checks), "
|
||||
"[This explains some of the clazy warnings](https://www.kdab.com/uncovering-32-qt-best-practices-compile-time-clazy/)"
|
||||
)
|
||||
report_lines.append("````")
|
||||
report_lines.append(aggregated_output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run Clazy on provided C++ files and append a Markdown report."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--files",
|
||||
required=True,
|
||||
help="A space-separated list (or glob-expanded string) of C++ files to check.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--clazy-checks",
|
||||
required=True,
|
||||
help="Comma-separated list of clazy checks to run.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--log-dir", required=True, help="Directory where the log file will be written."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--report-file",
|
||||
required=True,
|
||||
help="Path to the Markdown report file to append results.",
|
||||
)
|
||||
parser.add_argument("--verbose", action="store_true", help="Enable verbose output.")
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
build_compile_commands = "build/compile_commands.json"
|
||||
fix_file = "clazy.yaml"
|
||||
|
||||
output = ""
|
||||
file_list = args.files.split()
|
||||
|
||||
for file in file_list:
|
||||
cmd = [
|
||||
"clazy-standalone",
|
||||
f"--export-fixes={fix_file}",
|
||||
f"-checks={args.clazy_checks}",
|
||||
"-p",
|
||||
build_compile_commands,
|
||||
file,
|
||||
]
|
||||
stdout, stderr, _ = run_command(cmd)
|
||||
output += stdout + "\n" + stderr + "\n"
|
||||
|
||||
log_file = os.path.join(args.log_dir, "clazy.log")
|
||||
write_file(log_file, output)
|
||||
emit_problem_matchers(log_file, "clang.json", "clang")
|
||||
|
||||
error_pattern = r"^.+:\d+:\d+: error: .+$"
|
||||
warning_pattern = r"^.+:\d+:\d+: warning: .+$"
|
||||
note_pattern = r"^.+:\d+:\d+: note: .+$"
|
||||
errors_count = len(re.findall(error_pattern, output, re.MULTILINE))
|
||||
warnings_count = len(re.findall(warning_pattern, output, re.MULTILINE))
|
||||
notes_count = len(re.findall(note_pattern, output, re.MULTILINE))
|
||||
|
||||
logging.info(
|
||||
f"Found {errors_count} errors, {warnings_count} warnings, {notes_count} notes"
|
||||
)
|
||||
|
||||
report = generate_markdown_report(output, errors_count, warnings_count, notes_count)
|
||||
append_file(args.report_file, report + "\n")
|
||||
|
||||
sys.exit(0 if errors_count == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
120
tools/lint/clazy_qt6.py
Normal file
120
tools/lint/clazy_qt6.py
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
write_file,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
)
|
||||
|
||||
|
||||
def count_occurrences(pattern, text):
|
||||
matches = re.findall(pattern, text, re.MULTILINE)
|
||||
logging.debug(f"Found {len(matches)} matches for pattern: {pattern}")
|
||||
return len(matches)
|
||||
|
||||
|
||||
def generate_markdown_report(
|
||||
output: str, errors_count: int, warnings_count: int, notes_count: int
|
||||
) -> str:
|
||||
"""Generate a Markdown report based on Clazy QT6 output and issue counts."""
|
||||
report_lines = []
|
||||
if errors_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:fire: Clazy found :fire: {errors_count} errors, "
|
||||
f":warning: {warnings_count} warnings and :pencil2: {notes_count} notes for porting to QT6</summary>"
|
||||
)
|
||||
elif warnings_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:warning: Clazy found :warning: {warnings_count} warnings and "
|
||||
f":pencil2: {notes_count} notes for porting to QT6</summary>"
|
||||
)
|
||||
elif notes_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Clazy found :pencil2: {notes_count} notes for porting to QT6</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Clazy found no errors, warnings or notes for porting to QT6</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run Clazy checks for QT6 on provided C++ files and append a Markdown report."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--files",
|
||||
required=True,
|
||||
help="A space-separated list (or glob-expanded string) of C++ files to check.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--clazy-qt6-checks",
|
||||
required=True,
|
||||
help="Comma-separated list of clazy QT6 checks to run.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--log-dir", required=True, help="Directory where the log file will be written."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--report-file",
|
||||
required=True,
|
||||
help="Path to the Markdown report file to append results.",
|
||||
)
|
||||
parser.add_argument("--verbose", action="store_true", help="Enable verbose output.")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Initialize logging and required directories using a shared utility.
|
||||
init_environment(args)
|
||||
|
||||
build_dir = "build"
|
||||
fix_file = "clazyQT6.yaml"
|
||||
|
||||
# Build the clazy command as a list.
|
||||
cmd = [
|
||||
"clazy-standalone",
|
||||
f"--export-fixes={fix_file}",
|
||||
f"-checks={args.clazy_qt6_checks}",
|
||||
"-p",
|
||||
build_dir,
|
||||
] + args.files.split()
|
||||
|
||||
stdout, stderr, _ = run_command(cmd)
|
||||
output = stdout + stderr
|
||||
|
||||
log_file = os.path.join(args.log_dir, "clazyQT6.log")
|
||||
write_file(log_file, output)
|
||||
emit_problem_matchers(log_file, "clang.json", "clang")
|
||||
|
||||
error_pattern = r"^.+:\d+:\d+: error: .+$"
|
||||
warning_pattern = r"^.+:\d+:\d+: warning: .+$"
|
||||
note_pattern = r"^.+:\d+:\d+: note: .+$"
|
||||
errors_count = count_occurrences(error_pattern, output)
|
||||
warnings_count = count_occurrences(warning_pattern, output)
|
||||
notes_count = count_occurrences(note_pattern, output)
|
||||
|
||||
logging.info(
|
||||
f"Found {errors_count} errors, {warnings_count} warnings, {notes_count} notes"
|
||||
)
|
||||
|
||||
report = generate_markdown_report(output, errors_count, warnings_count, notes_count)
|
||||
append_file(args.report_file, report + "\n")
|
||||
|
||||
sys.exit(0 if errors_count == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
133
tools/lint/codespell.py
Normal file
133
tools/lint/codespell.py
Normal file
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import urllib.request
|
||||
import logging
|
||||
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
write_file,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
)
|
||||
|
||||
|
||||
def download_dictionary(url, dest):
|
||||
logging.info(f"Downloading dictionary from {url} to {dest}")
|
||||
try:
|
||||
urllib.request.urlretrieve(url, dest)
|
||||
except Exception as e:
|
||||
logging.error(f"Error downloading dictionary: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def generate_markdown_report(misspellings: int, log_file: str) -> str:
|
||||
"""
|
||||
Generate a Markdown report section based on the codespell results and log file.
|
||||
"""
|
||||
report_lines = []
|
||||
if misspellings > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Codespell found {misspellings} misspellings</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Codespell found no misspellings</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append(
|
||||
"To ignore false positives, append the word to the [.github/codespellignore]"
|
||||
"(https://github.com/FreeCAD/FreeCAD/blob/master/.github/codespellignore) file (lowercase)"
|
||||
)
|
||||
report_lines.append("````")
|
||||
with open(log_file, "r", encoding="utf-8") as lf:
|
||||
report_lines.append(lf.read())
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run codespell on files and append a Markdown report."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--files",
|
||||
action="extend",
|
||||
nargs="+",
|
||||
required=True,
|
||||
help="List of files to spell check."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ignore-words",
|
||||
required=True,
|
||||
help="Comma-separated list of words to ignore (from codespellignore).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--log-dir",
|
||||
required=True,
|
||||
help="Directory to store the log file."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--report-file",
|
||||
required=True,
|
||||
help="Name of the report file."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip", required=True, help="Comma-separated list of file patterns to skip."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--verbose",
|
||||
action='store_true',
|
||||
help="Use verbose output."
|
||||
)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
dictionary_file = "dictionary.txt"
|
||||
if not os.path.exists(dictionary_file):
|
||||
dictionary_url = "https://raw.githubusercontent.com/codespell-project/codespell/master/codespell_lib/data/dictionary.txt"
|
||||
logging.info("Dictionary not found; downloading...")
|
||||
download_dictionary(dictionary_url, dictionary_file)
|
||||
|
||||
run_command(["pip", "install", "-q", "codespell"], check=True)
|
||||
|
||||
cmd = [
|
||||
"codespell",
|
||||
"--quiet-level",
|
||||
"3",
|
||||
"--summary",
|
||||
"--count",
|
||||
"--ignore-words",
|
||||
args.ignore_words,
|
||||
"--skip",
|
||||
args.skip,
|
||||
"-D",
|
||||
dictionary_file,
|
||||
] + args.files
|
||||
stdout, stderr, _ = run_command(cmd)
|
||||
output = stdout + "\n" + stderr + "\n"
|
||||
|
||||
log_file = os.path.join(args.log_dir, "codespell.log")
|
||||
write_file(log_file, output)
|
||||
emit_problem_matchers(log_file, "codespell.json", "codespell")
|
||||
|
||||
try:
|
||||
misspellings = int(stdout.strip())
|
||||
except ValueError:
|
||||
logging.info(f"Could not parse misspellings count from output:\n{stdout}")
|
||||
misspellings = 0
|
||||
|
||||
logging.info(f"Found {misspellings} misspellings")
|
||||
|
||||
report = generate_markdown_report(misspellings, log_file)
|
||||
append_file(args.report_file, report + "\n")
|
||||
|
||||
sys.exit(0 if misspellings == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
65
tools/lint/cpplint.cfg
Normal file
65
tools/lint/cpplint.cfg
Normal file
@@ -0,0 +1,65 @@
|
||||
+build/deprecated,
|
||||
-build/endif_comment,
|
||||
+build/explicit_make_pair,
|
||||
-build/forward_decl,
|
||||
-build/header_guard,
|
||||
+build/include,
|
||||
-build/include_subdir,
|
||||
-build/include_alpha,
|
||||
-build/include_order,
|
||||
+build/include_what_you_use,
|
||||
+build/namespaces_headers,
|
||||
+build/namespaces_literals,
|
||||
-build/namespaces,
|
||||
+build/printf_format,
|
||||
+build/storage_class,
|
||||
-legal/copyright,
|
||||
+readability/alt_tokens,
|
||||
-readability/braces,
|
||||
+readability/casting,
|
||||
+readability/check,
|
||||
+readability/constructors,
|
||||
+readability/fn_size,
|
||||
+readability/inheritance,
|
||||
+readability/multiline_comment,
|
||||
+readability/multiline_string,
|
||||
+readability/namespace,
|
||||
-readability/nolint,
|
||||
+readability/nul,
|
||||
+readability/strings,
|
||||
-readability/todo,
|
||||
+readability/utf8,
|
||||
+runtime/arrays,
|
||||
+runtime/casting,
|
||||
+runtime/explicit,
|
||||
+runtime/int,
|
||||
+runtime/init,
|
||||
+runtime/invalid_increment,
|
||||
+runtime/member_string_references,
|
||||
+runtime/memset,
|
||||
+runtime/operator,
|
||||
+runtime/printf,
|
||||
+runtime/printf_format,
|
||||
+runtime/references,
|
||||
+runtime/string,
|
||||
+runtime/threadsafe_fn,
|
||||
+runtime/vlog,
|
||||
-whitespace/blank_line,
|
||||
-whitespace/braces,
|
||||
-whitespace/comma,
|
||||
-whitespace/comments,
|
||||
-whitespace/empty_conditional_body,
|
||||
-whitespace/empty_if_body,
|
||||
-whitespace/empty_loop_body,
|
||||
-whitespace/end_of_line,
|
||||
-whitespace/ending_newline,
|
||||
-whitespace/forcolon,
|
||||
-whitespace/indent,
|
||||
-whitespace/indent_namespace,
|
||||
-whitespace/line_length,
|
||||
-whitespace/newline,
|
||||
-whitespace/operators,
|
||||
-whitespace/parens,
|
||||
-whitespace/semicolon,
|
||||
-whitespace/tab,
|
||||
-whitespace/todo
|
||||
90
tools/lint/cpplint.py
vendored
Normal file
90
tools/lint/cpplint.py
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
write_file,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
add_common_arguments,
|
||||
)
|
||||
|
||||
DEFAULT_LINE_LENGTH_LIMIT = 100
|
||||
|
||||
def load_cpplint_filters(conf_file="cpplint.cfg"):
|
||||
"""Load cpplint filters from a configuration file."""
|
||||
try:
|
||||
with open(conf_file, "r", encoding="utf-8") as f:
|
||||
filters = f.read().strip()
|
||||
return filters
|
||||
except Exception as e:
|
||||
logging.warning(f"Could not load {conf_file}: {e}")
|
||||
return ""
|
||||
|
||||
def count_cpplint_errors(log_text: str) -> int:
|
||||
"""
|
||||
Count the number of cpplint error/warning lines.
|
||||
"""
|
||||
matches = re.findall(r"\[[0-9]+\]$", log_text, re.MULTILINE)
|
||||
return len(matches)
|
||||
|
||||
def generate_markdown_report(aggregated_output: str, errors_count: int) -> str:
|
||||
"""Generate a Markdown report section based on cpplint output and error count."""
|
||||
report_lines = []
|
||||
if errors_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:warning: CppLint found {errors_count} errors / warnings</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: No cpplint errors found </summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(aggregated_output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run cpplint on provided C++ files and append a Markdown report."
|
||||
)
|
||||
add_common_arguments(parser)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
run_command(["pip", "install", "-q", "cpplint"], check=True)
|
||||
|
||||
cpplint_filters = load_cpplint_filters("cpplint.cfg")
|
||||
|
||||
aggregated_output = ""
|
||||
for file in args.files.split():
|
||||
cmd = [
|
||||
"cpplint",
|
||||
f"--filters={cpplint_filters}",
|
||||
f"--linelength={DEFAULT_LINE_LENGTH_LIMIT}",
|
||||
file,
|
||||
]
|
||||
stdout, stderr, _ = run_command(cmd)
|
||||
aggregated_output += stdout + stderr + "\n"
|
||||
|
||||
cpplint_log = os.path.join(args.log_dir, "cpplint.log")
|
||||
write_file(cpplint_log, aggregated_output)
|
||||
emit_problem_matchers(cpplint_log, "cpplint.json", "cpplint")
|
||||
|
||||
errors_count = count_cpplint_errors(aggregated_output)
|
||||
logging.info(f"Found {errors_count} cpplint errors")
|
||||
|
||||
report = generate_markdown_report(aggregated_output, errors_count)
|
||||
append_file(args.report_file, report)
|
||||
|
||||
sys.exit(0 if errors_count == 0 else 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
165
tools/lint/generic_checks.py
Executable file
165
tools/lint/generic_checks.py
Executable file
@@ -0,0 +1,165 @@
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
from utils import (
|
||||
add_common_arguments,
|
||||
init_environment,
|
||||
write_file,
|
||||
emit_problem_matchers,
|
||||
)
|
||||
|
||||
|
||||
def check_line_endings(file_paths):
|
||||
"""Check if files have non-Unix (CRLF or CR) line endings.
|
||||
|
||||
Returns a dict mapping file path to an issue description.
|
||||
"""
|
||||
issues = {}
|
||||
for path in file_paths:
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
content = f.read()
|
||||
if b"\r\n" in content or (b"\r" in content and b"\n" not in content):
|
||||
issues[path] = "File has non-Unix line endings."
|
||||
except Exception as e:
|
||||
issues[path] = f"Error reading file: {e}"
|
||||
return issues
|
||||
|
||||
|
||||
def check_trailing_whitespace(file_paths):
|
||||
"""Check for trailing whitespace in files.
|
||||
|
||||
Returns a dict mapping file paths to a list of line numbers with trailing whitespace.
|
||||
"""
|
||||
issues = {}
|
||||
for path in file_paths:
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
lines = f.read().splitlines()
|
||||
error_lines = [
|
||||
idx
|
||||
for idx, line in enumerate(lines, start=1)
|
||||
if line != line.rstrip()
|
||||
]
|
||||
if error_lines:
|
||||
issues[path] = error_lines
|
||||
except Exception as e:
|
||||
issues[path] = f"Error reading file: {e}"
|
||||
return issues
|
||||
|
||||
|
||||
def check_tabs(file_paths):
|
||||
"""Check for usage of tab characters in files.
|
||||
|
||||
Returns a dict mapping file paths to a list of line numbers containing tabs.
|
||||
"""
|
||||
issues = {}
|
||||
for path in file_paths:
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
lines = f.read().splitlines()
|
||||
tab_lines = [
|
||||
idx for idx, line in enumerate(lines, start=1) if "\t" in line
|
||||
]
|
||||
if tab_lines:
|
||||
issues[path] = tab_lines
|
||||
except Exception as e:
|
||||
issues[path] = f"Error reading file: {e}"
|
||||
return issues
|
||||
|
||||
|
||||
def format_report(section_title, issues):
|
||||
"""Format a report section with a Markdown details block."""
|
||||
report = []
|
||||
if issues:
|
||||
count = len(issues)
|
||||
report.append(
|
||||
f"<details><summary>:information_source: Found {count} issue(s) in {section_title}</summary>\n"
|
||||
)
|
||||
report.append("```")
|
||||
for file, details in issues.items():
|
||||
report.append(f"{file}: {details}")
|
||||
report.append("```\n</details>\n")
|
||||
else:
|
||||
report.append(f":heavy_check_mark: No issues found in {section_title}\n")
|
||||
return "\n".join(report)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Run formatting lint checks.")
|
||||
add_common_arguments(parser)
|
||||
parser.add_argument(
|
||||
"--lineendings-check",
|
||||
action="store_true",
|
||||
help="Enable check for non-Unix line endings.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--whitespace-check",
|
||||
action="store_true",
|
||||
help="Enable check for trailing whitespace.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tabs-check", action="store_true", help="Enable check for tab characters."
|
||||
)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
file_list = glob.glob(args.files, recursive=True)
|
||||
file_list = [f for f in file_list if os.path.isfile(f)]
|
||||
|
||||
report_sections = []
|
||||
|
||||
# Check non-Unix line endings.
|
||||
if args.lineendings_check:
|
||||
le_issues = check_line_endings(file_list)
|
||||
for file, detail in le_issues.items():
|
||||
print(f"::warning file={file},title={file}::{detail}")
|
||||
report_sections.append(format_report("Non-Unix Line Endings", le_issues))
|
||||
|
||||
# Check trailing whitespace.
|
||||
if args.whitespace_check:
|
||||
ws_issues = check_trailing_whitespace(file_list)
|
||||
if ws_issues:
|
||||
ws_output_lines = []
|
||||
for file, details in ws_issues.items():
|
||||
if isinstance(details, list):
|
||||
for line in details:
|
||||
ws_output_lines.append(f"{file}:{line}: trailing whitespace")
|
||||
else:
|
||||
ws_output_lines.append(f"{file}: {details}")
|
||||
ws_output = "\n".join(ws_output_lines)
|
||||
|
||||
ws_log_file = os.path.join(args.log_dir, "whitespace.log")
|
||||
write_file(ws_log_file, ws_output)
|
||||
emit_problem_matchers(
|
||||
ws_log_file, "grepMatcherWarning.json", "grepMatcher-warning"
|
||||
)
|
||||
report_sections.append(format_report("Trailing Whitespace", ws_issues))
|
||||
|
||||
# Check tab usage.
|
||||
if args.tabs_check:
|
||||
tab_issues = check_tabs(file_list)
|
||||
if tab_issues:
|
||||
tab_output_lines = []
|
||||
for file, details in tab_issues.items():
|
||||
if isinstance(details, list):
|
||||
for line in details:
|
||||
tab_output_lines.append(f"{file}:{line}: contains tab")
|
||||
else:
|
||||
tab_output_lines.append(f"{file}: {details}")
|
||||
tab_output = "\n".join(tab_output_lines)
|
||||
|
||||
tab_log_file = os.path.join(args.log_dir, "tabs.log")
|
||||
write_file(tab_log_file, tab_output)
|
||||
emit_problem_matchers(
|
||||
tab_log_file, "grepMatcherWarning.json", "grepMatcher-warning"
|
||||
)
|
||||
report_sections.append(format_report("Tab Usage", tab_issues))
|
||||
|
||||
report_content = "\n".join(report_sections)
|
||||
write_file(args.report_file, report_content)
|
||||
print("Lint report generated at:", args.report_file)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
160
tools/lint/pylint.py
Executable file
160
tools/lint/pylint.py
Executable file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
import logging
|
||||
from utils import (
|
||||
run_command,
|
||||
add_common_arguments,
|
||||
init_environment,
|
||||
write_file,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
)
|
||||
|
||||
|
||||
def get_enabled_checks(log_dir: str) -> str:
|
||||
"""Run pylint to list enabled messages and write to a log file."""
|
||||
enabled_log_path = os.path.join(log_dir, "pylint-enabled-checks.log")
|
||||
stdout, _, _ = run_command(["pylint", "--list-msgs-enabled"])
|
||||
write_file(enabled_log_path, stdout)
|
||||
return enabled_log_path
|
||||
|
||||
|
||||
def run_pylint(files: str, disable: str, log_dir: str) -> str:
|
||||
"""Run pylint on the provided files and log the output."""
|
||||
pylint_log_path = os.path.join(log_dir, "pylint.log")
|
||||
cmd = ["pylint", f"--disable={disable}"] + files.split()
|
||||
stdout, stderr, _ = run_command(cmd, check=False)
|
||||
combined_output = stdout + "\n" + stderr
|
||||
write_file(pylint_log_path, combined_output)
|
||||
return pylint_log_path
|
||||
|
||||
|
||||
def count_pattern(pattern: str, text: str) -> int:
|
||||
"""Count numeric values found by a regex pattern in the text."""
|
||||
matches = re.findall(pattern, text)
|
||||
logging.debug("Pattern '%s' found matches: %s", pattern, matches)
|
||||
return sum(int(match) for match in matches) if matches else 0
|
||||
|
||||
|
||||
def parse_pylint_output(log_path: str) -> dict:
|
||||
"""Parse pylint log file for error, warning, refactor, and convention counts."""
|
||||
with open(log_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
errors = count_pattern(r"error\s+\|(\d+)", content)
|
||||
warnings = count_pattern(r"warning\s+\|(\d+)", content)
|
||||
refactorings = count_pattern(r"refactor\s+\|(\d+)", content)
|
||||
conventions = count_pattern(r"convention\s+\|(\d+)", content)
|
||||
logging.info(
|
||||
"Pylint issues - Errors: %s, Warnings: %s, Refactorings: %s, Conventions: %s",
|
||||
errors,
|
||||
warnings,
|
||||
refactorings,
|
||||
conventions,
|
||||
)
|
||||
return {
|
||||
"errors": errors,
|
||||
"warnings": warnings,
|
||||
"refactorings": refactorings,
|
||||
"conventions": conventions,
|
||||
"full_output": content,
|
||||
}
|
||||
|
||||
|
||||
def generate_markdown_report(
|
||||
pylint_counts: dict, enabled_checks_log: str, pylint_log: str
|
||||
) -> str:
|
||||
"""Generate a Markdown-formatted report based on pylint results and logs."""
|
||||
|
||||
def generate_summary(counts: dict) -> str:
|
||||
errors = counts.get("errors", 0)
|
||||
warnings = counts.get("warnings", 0)
|
||||
refactorings = counts.get("refactorings", 0)
|
||||
conventions = counts.get("conventions", 0)
|
||||
if errors:
|
||||
return (
|
||||
f":fire: Pylint found :fire: {errors} errors, "
|
||||
f":warning: {warnings} warnings, :construction: {refactorings} refactorings and "
|
||||
f":pencil2: {conventions} conventions"
|
||||
)
|
||||
if warnings:
|
||||
return (
|
||||
f":warning: Pylint found :warning: {warnings} warnings, "
|
||||
f":construction: {refactorings} refactorings and "
|
||||
f":pencil2: {conventions} conventions"
|
||||
)
|
||||
if refactorings:
|
||||
return (
|
||||
f":construction: Pylint found :construction: {refactorings} refactorings and "
|
||||
f":pencil2: {conventions} conventions"
|
||||
)
|
||||
if conventions:
|
||||
return f":pencil2: Pylint found :pencil2: {conventions} conventions"
|
||||
return ":heavy_check_mark: No pylint errors found"
|
||||
|
||||
def read_file_contents(file_path: str) -> str:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
report_lines = []
|
||||
summary = generate_summary(pylint_counts)
|
||||
report_lines.append(f"<details><summary>{summary}</summary>")
|
||||
report_lines.append("")
|
||||
report_lines.append(
|
||||
"<details><summary>:information_source: Enabled checks</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(read_file_contents(enabled_checks_log))
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
report_lines.append("<details>")
|
||||
report_lines.append("````")
|
||||
report_lines.append(read_file_contents(pylint_log))
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("</details>")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run Pylint on provided Python files and append a Markdown report."
|
||||
)
|
||||
add_common_arguments(parser)
|
||||
parser.add_argument(
|
||||
"--disable",
|
||||
default="C0302,C0303",
|
||||
help="Comma-separated list of pylint checks to disable.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
logging.info("Installing pylint (if needed)...")
|
||||
run_command(["pip", "install", "-q", "pylint"], check=True)
|
||||
|
||||
enabled_checks_log = get_enabled_checks(args.log_dir)
|
||||
pylint_log = run_pylint(args.files, args.disable, args.log_dir)
|
||||
|
||||
emit_problem_matchers(pylint_log, "pylintError.json", "pylint-error")
|
||||
emit_problem_matchers(pylint_log, "pylintWarning.json", "pylint-warning")
|
||||
|
||||
pylint_counts = parse_pylint_output(pylint_log)
|
||||
|
||||
summary_msg = (
|
||||
f"Found {pylint_counts['errors']} errors, {pylint_counts['warnings']} warnings, "
|
||||
f"{pylint_counts['refactorings']} refactorings, {pylint_counts['conventions']} conventions"
|
||||
)
|
||||
logging.info(summary_msg)
|
||||
|
||||
report = generate_markdown_report(pylint_counts, enabled_checks_log, pylint_log)
|
||||
append_file(args.report_file, report)
|
||||
|
||||
sys.exit(0 if pylint_counts["errors"] == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
75
tools/lint/qt_connections.py
Normal file
75
tools/lint/qt_connections.py
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from utils import (
|
||||
add_common_arguments,
|
||||
init_environment,
|
||||
write_file,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
)
|
||||
|
||||
|
||||
def check_qt_connections(file_path):
|
||||
"""
|
||||
Scan the file for lines containing 'SIGNAL' or 'SLOT' and return a list of matching lines
|
||||
with line numbers and a suggestion.
|
||||
"""
|
||||
matches = []
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
||||
for lineno, line in enumerate(f, start=1):
|
||||
if re.search(r"\b(SIGNAL|SLOT)\b", line):
|
||||
matches.append(
|
||||
f"{file_path}:{lineno}: {line.rstrip()} <-- Consider using functor-based connections"
|
||||
)
|
||||
except Exception as e:
|
||||
logging.debug(f"Error reading {file_path}: {e}")
|
||||
return matches
|
||||
|
||||
|
||||
def generate_markdown_report(all_matches):
|
||||
"""Generate a Markdown report based on the list of QT connection matches."""
|
||||
count = len(all_matches)
|
||||
if count > 0:
|
||||
md_report = (
|
||||
f"<details><summary>:information_source: Found {count} QT string-based connections → consider using QT functor-based connections</summary>\n"
|
||||
"\nFor more information see: https://wiki.qt.io/New_Signal_Slot_Syntax or https://github.com/FreeCAD/FreeCAD/issues/6166\n"
|
||||
"```\n" + "\n".join(all_matches) + "\n```\n</details>\n\n"
|
||||
)
|
||||
else:
|
||||
md_report = ":heavy_check_mark: No string-based connections found\n\n"
|
||||
return md_report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check for old Qt string-based connections in files and append a Markdown report."
|
||||
)
|
||||
add_common_arguments(parser)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
all_matches = []
|
||||
for file_path in args.files.split():
|
||||
logging.debug(f"Checking file: {file_path}")
|
||||
matches = check_qt_connections(file_path)
|
||||
all_matches.extend(matches)
|
||||
|
||||
log_file = os.path.join(args.log_dir, "qtConnections.log")
|
||||
write_file(log_file, "\n".join(all_matches))
|
||||
emit_problem_matchers(log_file, "grepMatcherWarning.json", "grepMatcher-warning")
|
||||
|
||||
count = len(all_matches)
|
||||
logging.info(f"Found {count} QT string-based connections")
|
||||
|
||||
md_report = generate_markdown_report(all_matches)
|
||||
append_file(args.report_file, md_report)
|
||||
|
||||
sys.exit(0 if count == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
95
tools/lint/utils.py
Normal file
95
tools/lint/utils.py
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import subprocess
|
||||
import argparse
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
def run_command(cmd, check=False) -> Tuple[str, str, int]:
|
||||
"""
|
||||
Run a command using subprocess.run and return stdout, stderr, and exit code.
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=check, text=True
|
||||
)
|
||||
return result.stdout, result.stderr, result.returncode
|
||||
except subprocess.CalledProcessError as e:
|
||||
return e.stdout, e.stderr, e.returncode
|
||||
|
||||
|
||||
def setup_logger(verbose: bool):
|
||||
"""
|
||||
Setup the logging level based on the verbose flag.
|
||||
"""
|
||||
level = logging.DEBUG if verbose else logging.INFO
|
||||
logging.basicConfig(level=level, format="%(levelname)s: %(message)s")
|
||||
|
||||
|
||||
def write_file(file_path: str, content: str):
|
||||
"""Write content to the specified file."""
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
logging.info("Wrote file: %s", file_path)
|
||||
|
||||
|
||||
def append_file(file_path: str, content: str):
|
||||
"""Append content to the specified file."""
|
||||
with open(file_path, "a", encoding="utf-8") as f:
|
||||
f.write(content + "\n")
|
||||
logging.info("Appended content to file: %s", file_path)
|
||||
|
||||
|
||||
def emit_problem_matchers(log_path: str, matcher_filename: str, remove_owner: str):
|
||||
"""
|
||||
Emit GitHub Actions problem matcher commands using the given matcher file.
|
||||
|
||||
This function will:
|
||||
1. Check if the log file exists.
|
||||
2. Use the RUNNER_WORKSPACE environment variable to construct the matcher path.
|
||||
3. Print the add-matcher command, then the log content, then the remove-matcher command.
|
||||
"""
|
||||
if os.path.isfile(log_path):
|
||||
runner_workspace = os.getenv("RUNNER_WORKSPACE")
|
||||
matcher_path = os.path.join(
|
||||
runner_workspace, "FreeCAD", ".github", "problemMatcher", matcher_filename
|
||||
)
|
||||
print(f"::add-matcher::{matcher_path}")
|
||||
with open(log_path, "r", encoding="utf-8") as f:
|
||||
sys.stdout.write(f.read())
|
||||
print(f"::remove-matcher owner={remove_owner}::")
|
||||
|
||||
|
||||
def add_common_arguments(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
||||
"""
|
||||
Add common command-line arguments shared between tools.
|
||||
"""
|
||||
parser.add_argument(
|
||||
"--files",
|
||||
required=True,
|
||||
help="A space-separated list or glob pattern of files to check.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--log-dir", required=True, help="Directory where log files will be written."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--report-file",
|
||||
required=True,
|
||||
help="Path to the Markdown report file to append results.",
|
||||
)
|
||||
parser.add_argument("--verbose", action="store_true", help="Enable verbose output.")
|
||||
return parser
|
||||
|
||||
|
||||
def init_environment(args: argparse.Namespace):
|
||||
"""
|
||||
Perform common initialization tasks:
|
||||
- Set up logging.
|
||||
- Create the log directory.
|
||||
- Create the directory for the report file.
|
||||
"""
|
||||
setup_logger(args.verbose)
|
||||
os.makedirs(args.log_dir, exist_ok=True)
|
||||
os.makedirs(os.path.dirname(args.report_file), exist_ok=True)
|
||||
307
tools/prepare_web_dependencies.sh
Executable file
307
tools/prepare_web_dependencies.sh
Executable file
@@ -0,0 +1,307 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
workspace_dir=$(cd "$source_dir/.." && pwd)
|
||||
base_lib_dir=${FREECAD_WEB_BASE_LIB_DIR:-"$workspace_dir/FreeCADBaseLib"}
|
||||
build_root=${FREECAD_WEB_BUILD_ROOT:-"$source_dir/build-web"}
|
||||
jobs=${FREECAD_WEB_JOBS:-8}
|
||||
|
||||
yaml_source="$base_lib_dir/sources/src/YAML-CPP-0.8.0"
|
||||
eigen_source="$base_lib_dir/sources/src/Eigen-3.4.0"
|
||||
boost_source="$base_lib_dir/sources/src/Boost-1.83.0"
|
||||
zlib_source="$base_lib_dir/sources/src/zlib-1.3"
|
||||
xerces_source="$base_lib_dir/sources/src/Xerces-C-3.2.4"
|
||||
qt_source="$base_lib_dir/sources/src/Qt-5.15.13/qtbase"
|
||||
python_source="$base_lib_dir/sources/src/Python-3.12.3"
|
||||
|
||||
yaml_build="$build_root/yaml-cpp"
|
||||
yaml_install="$build_root/yaml-install"
|
||||
eigen_build="$build_root/eigen"
|
||||
eigen_install="$build_root/eigen-install"
|
||||
boost_build="$build_root/boost-build"
|
||||
boost_install="$build_root/boost-install"
|
||||
zlib_build="$build_root/zlib"
|
||||
zlib_install="$build_root/zlib-install"
|
||||
xerces_build="$build_root/xerces"
|
||||
xerces_install="$build_root/xerces-install"
|
||||
qt_build="$build_root/qt-probe"
|
||||
qt_install="$build_root/qt-install"
|
||||
python_build="$build_root/python-probe"
|
||||
python_install="$build_root/python-install"
|
||||
icu_install="$build_root/icu-install"
|
||||
occt_install="$build_root/occt-install"
|
||||
icu_download="$build_root/icu-download/icu4c-68_2-src.tgz"
|
||||
icu_port_dir="$build_root/emscripten-cache/ports/icu"
|
||||
emscripten_cache="$build_root/emscripten-cache"
|
||||
|
||||
icu_domestic_url=${FREECAD_WEB_ICU_DOMESTIC_URL:-"https://mirrors.aliyun.com/blfs/conglomeration/icu/icu4c-68_2-src.tgz"}
|
||||
icu_official_url=${FREECAD_WEB_ICU_OFFICIAL_URL:-"https://github.com/unicode-org/icu/releases/download/release-68-2/icu4c-68_2-src.zip"}
|
||||
icu_domestic_sha512="24f3b7981667b37df58118f56ae76a09b719358c1efa8d6f044d28ef8df1c722a894957acadfb3c37aee39ac845893924fc3370d6bbe09875aec8d937059f84b"
|
||||
icu_official_sha512="12c3db5966c234c94e7918fb8acc8bd0838edc36a620f3faa788e7ff27b06f1aa431eb117401026e3963622b9323212f444b735d5c9dd3d0b82d772a4834b993"
|
||||
icu_archive_format=tgz
|
||||
|
||||
mkdir -p "$build_root" "$source_dir/../docs" "$emscripten_cache"
|
||||
exec > >(tee "$source_dir/../docs/web-dependency-install.log") 2>&1
|
||||
printf 'ICU domestic mirror preference: %s\n' "$icu_domestic_url"
|
||||
printf 'ICU official fallback: %s\n' "$icu_official_url"
|
||||
|
||||
if [[ ! -f "$yaml_source/.source-complete" ||
|
||||
! -f "$boost_source/.source-complete" ||
|
||||
! -f "$eigen_source/.source-complete" ||
|
||||
! -f "$zlib_source/.source-complete" ||
|
||||
! -f "$xerces_source/.source-complete" ||
|
||||
! -f "$qt_source/configure" ||
|
||||
! -f "$python_source/configure" ]]; then
|
||||
"$base_lib_dir/download_sources.sh"
|
||||
fi
|
||||
|
||||
toolchain=${FREECAD_WEB_EMSCRIPTEN_TOOLCHAIN:-}
|
||||
if [[ -z "$toolchain" ]]; then
|
||||
emcc_path=$(realpath "$(command -v emcc)")
|
||||
toolchain="$(dirname "$emcc_path")/cmake/Modules/Platform/Emscripten.cmake"
|
||||
fi
|
||||
if [[ ! -f "$toolchain" ]]; then
|
||||
printf 'Emscripten toolchain not found: %s\n' "$toolchain" >&2
|
||||
exit 1
|
||||
fi
|
||||
emscripten_root=$(cd "$(dirname "$toolchain")/../../.." && pwd)
|
||||
emscripten_config=${FREECAD_WEB_EMSCRIPTEN_CONFIG:-"$build_root/emscripten-config.py"}
|
||||
if [[ -z "${FREECAD_WEB_EMSCRIPTEN_CONFIG:-}" ]] &&
|
||||
{ [[ ! -f "$emscripten_config" ]] ||
|
||||
! grep -Fq "CACHE = r'$emscripten_cache'" "$emscripten_config"; }; then
|
||||
cp "$emscripten_root/.emscripten" "$emscripten_config"
|
||||
sed -i "s|^FROZEN_CACHE = True.*|FROZEN_CACHE = False\nCACHE = r'$emscripten_cache'\nPORTS = r'$emscripten_cache/ports'|" \
|
||||
"$emscripten_config"
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$icu_download")"
|
||||
if [[ ! -f "$icu_download" ]]; then
|
||||
printf 'Downloading ICU from domestic mirror: %s\n' "$icu_domestic_url"
|
||||
if ! curl --fail --location --retry 2 --connect-timeout 10 --max-time 600 \
|
||||
"$icu_domestic_url" -o "$icu_download"; then
|
||||
printf 'Domestic ICU mirror failed; falling back to official source: %s\n' "$icu_official_url"
|
||||
icu_archive_format=zip
|
||||
curl --fail --location --retry 2 --connect-timeout 10 --max-time 600 \
|
||||
"$icu_official_url" -o "$icu_download"
|
||||
fi
|
||||
fi
|
||||
actual_icu_sha512=$(sha512sum "$icu_download" | awk '{print $1}')
|
||||
printf 'ICU source archive: %s (SHA-512 %s)\n' "$icu_download" "$actual_icu_sha512"
|
||||
if [[ "$actual_icu_sha512" != "$icu_domestic_sha512" &&
|
||||
"$actual_icu_sha512" != "$icu_official_sha512" ]]; then
|
||||
printf 'Unexpected ICU archive SHA-512: %s\n' "$actual_icu_sha512" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$actual_icu_sha512" == "$icu_official_sha512" ]]; then
|
||||
icu_archive_format=zip
|
||||
else
|
||||
icu_archive_format=tgz
|
||||
fi
|
||||
|
||||
# Initialize the selected Emscripten cache before seeding the local port. A
|
||||
# changed config can clear the cache during this first compiler invocation.
|
||||
printf 'int main() { return 0; }\n' | \
|
||||
EM_CONFIG="$emscripten_config" EM_FROZEN_CACHE= \
|
||||
em++ -x c++ - -sWASM=1 -o "$build_root/emscripten-sanity.js"
|
||||
|
||||
# Emscripten 3.1.6's ICU port hard-codes a GitHub URL. Seed its local port
|
||||
# cache from the verified domestic archive so the compiler never needs that
|
||||
# slow network path during a Web dependency preparation run.
|
||||
if [[ ! -f "$icu_port_dir/icu/source/common/unicode/utypes.h" ]]; then
|
||||
mkdir -p "$icu_port_dir"
|
||||
if [[ "$icu_archive_format" == zip ]]; then
|
||||
unzip -q "$icu_download" -d "$icu_port_dir"
|
||||
else
|
||||
tar -xzf "$icu_download" -C "$icu_port_dir"
|
||||
fi
|
||||
fi
|
||||
printf '%s\n' "$icu_official_url" > "$icu_port_dir/.emscripten_url"
|
||||
|
||||
printf 'Building Emscripten ICU port from the verified local source\n'
|
||||
printf 'int main() { return 0; }\n' | \
|
||||
EM_CONFIG="$emscripten_config" EM_FROZEN_CACHE= EM_PORTS="$emscripten_cache/ports" \
|
||||
em++ -x c++ - -sUSE_ICU=1 -sWASM=1 \
|
||||
-o "$build_root/icu-port-smoke.js"
|
||||
mkdir -p "$icu_install/include" "$icu_install/lib"
|
||||
rm -rf "$icu_install/include/unicode"
|
||||
cp -a "$emscripten_cache/sysroot/include/unicode" "$icu_install/include/"
|
||||
cp "$emscripten_cache/sysroot/lib/wasm32-emscripten/libicu_common.a" "$icu_install/lib/libicuuc.a"
|
||||
cp "$emscripten_cache/sysroot/lib/wasm32-emscripten/libicu_i18n.a" "$icu_install/lib/libicui18n.a"
|
||||
cp "$emscripten_cache/sysroot/lib/wasm32-emscripten/libicu_stubdata.a" "$icu_install/lib/libicudata.a"
|
||||
|
||||
cmake -S "$yaml_source" -B "$yaml_build" -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$toolchain" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="$yaml_install" \
|
||||
-DYAML_BUILD_SHARED_LIBS=OFF \
|
||||
-DYAML_CPP_BUILD_TESTS=OFF \
|
||||
-DYAML_CPP_BUILD_TOOLS=OFF \
|
||||
-DYAML_CPP_INSTALL=ON
|
||||
cmake --build "$yaml_build" --parallel "$jobs"
|
||||
cmake --install "$yaml_build"
|
||||
|
||||
cmake -S "$eigen_source" -B "$eigen_build" -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="$eigen_install" \
|
||||
-DEIGEN_BUILD_DOC=OFF \
|
||||
-DBUILD_TESTING=OFF
|
||||
cmake --install "$eigen_build" --component Devel
|
||||
|
||||
boost_b2=${FREECAD_WEB_B2_EXECUTABLE:-}
|
||||
if [[ -z "$boost_b2" ]]; then
|
||||
boost_b2=$(command -v b2 || command -v bjam || true)
|
||||
fi
|
||||
if [[ -z "$boost_b2" ]]; then
|
||||
printf 'B2 executable not found; set FREECAD_WEB_B2_EXECUTABLE.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
boost_user_config="$build_root/boost-user-config.jam"
|
||||
printf 'using clang : wasm : %s ;\n' "$(command -v em++)" > "$boost_user_config"
|
||||
"$boost_b2" -q \
|
||||
--user-config="$boost_user_config" \
|
||||
--build-dir="$boost_build" \
|
||||
--prefix="$boost_install" \
|
||||
--layout=system -j"$jobs" \
|
||||
toolset=clang-wasm variant=release link=static runtime-link=static \
|
||||
threading=single cxxflags=-fexceptions linkflags=-fexceptions \
|
||||
--with-program_options --with-regex --with-thread --with-date_time install
|
||||
|
||||
if [[ ! -f "$zlib_build/Makefile" ]]; then
|
||||
mkdir -p "$zlib_build"
|
||||
cp -a "$zlib_source/." "$zlib_build/"
|
||||
(
|
||||
cd "$zlib_build"
|
||||
emconfigure ./configure --static --prefix="$zlib_install"
|
||||
)
|
||||
fi
|
||||
emmake make -C "$zlib_build" --jobs="$jobs"
|
||||
emmake make -C "$zlib_build" install
|
||||
|
||||
# Emscripten's link-only check_function_exists probes report arbitrary symbols
|
||||
# as present. Xerces-C would then omit its portable replacements for the
|
||||
# non-standard case-insensitive string functions and fail at compile time.
|
||||
cmake -S "$xerces_source" -B "$xerces_build" -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$toolchain" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="$xerces_install" \
|
||||
-DCMAKE_INSTALL_LIBDIR=lib \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-Dnetwork=OFF \
|
||||
-Dtranscoder=icu \
|
||||
-Dmessage-loader=icu \
|
||||
-Dmutex-manager=nothreads \
|
||||
-DHAVE_STRICMP=0 \
|
||||
-DHAVE_STRNICMP=0 \
|
||||
-DICU_ROOT="$icu_install" \
|
||||
-DICU_INCLUDE_DIR="$icu_install/include" \
|
||||
-DICU_UC_LIBRARY="$icu_install/lib/libicuuc.a" \
|
||||
-DICU_UC_LIBRARY_RELEASE="$icu_install/lib/libicuuc.a" \
|
||||
-DICU_DATA_LIBRARY="$icu_install/lib/libicudata.a" \
|
||||
-DICU_DATA_LIBRARY_RELEASE="$icu_install/lib/libicudata.a"
|
||||
cmake --build "$xerces_build" --target xerces-c --parallel "$jobs"
|
||||
# Installing the whole project also installs sample executables, which are
|
||||
# intentionally not built for the dependency package. Install only the
|
||||
# library, headers, and CMake package emitted by the src directory.
|
||||
cmake -DCMAKE_INSTALL_COMPONENT=runtime \
|
||||
-P "$xerces_build/src/cmake_install.cmake"
|
||||
cmake -DCMAKE_INSTALL_COMPONENT=development \
|
||||
-P "$xerces_build/src/cmake_install.cmake"
|
||||
cmake -DCMAKE_INSTALL_COMPONENT=Unspecified \
|
||||
-P "$xerces_build/src/cmake_install.cmake"
|
||||
|
||||
if [[ ! -f "$qt_build/Makefile" ]]; then
|
||||
mkdir -p "$qt_build"
|
||||
(
|
||||
cd "$qt_build"
|
||||
"$qt_source/configure" \
|
||||
-xplatform wasm-emscripten \
|
||||
-opensource -confirm-license -release -static \
|
||||
-no-feature-thread -nomake examples -nomake tests \
|
||||
-prefix "$qt_install"
|
||||
)
|
||||
fi
|
||||
make -C "$qt_build/src" --jobs="$jobs" sub-network sub-xml
|
||||
make -C "$qt_build/src" --jobs="$jobs" \
|
||||
sub-corelib-install_subtargets \
|
||||
sub-network-install_subtargets \
|
||||
sub-xml-install_subtargets
|
||||
make -C "$qt_build/src" --jobs="$jobs" sub-rcc
|
||||
make -C "$qt_build/src" sub-rcc-install_subtargets
|
||||
make -C "$qt_build" install_mkspecs
|
||||
install -m 755 "$qt_build/bin/qmake" "$qt_install/bin/qmake"
|
||||
install -m 644 "$qt_build/bin/qt.conf" "$qt_install/bin/qt.conf"
|
||||
|
||||
if [[ ! -f "$python_build/Makefile" ]]; then
|
||||
mkdir -p "$python_build"
|
||||
(
|
||||
cd "$python_build"
|
||||
CONFIG_SITE="$python_source/Tools/wasm/config.site-wasm32-emscripten" \
|
||||
EM_CACHE="$emscripten_cache" EM_FROZEN_CACHE= \
|
||||
emconfigure "$python_source/configure" -C \
|
||||
--host=wasm32-unknown-emscripten \
|
||||
--build=x86_64-pc-linux-gnu \
|
||||
--with-emscripten-target=browser \
|
||||
--with-build-python="${FREECAD_WEB_HOST_PYTHON_EXECUTABLE:-/usr/bin/python3.12}" \
|
||||
--without-ensurepip
|
||||
)
|
||||
fi
|
||||
EM_CACHE="$emscripten_cache" EM_FROZEN_CACHE= \
|
||||
make -C "$python_build" --jobs="$jobs"
|
||||
EM_CACHE="$emscripten_cache" EM_FROZEN_CACHE= \
|
||||
make -C "$python_build" --jobs="$jobs" prefix="$python_install" install
|
||||
|
||||
required_files=(
|
||||
"$yaml_install/lib/libyaml-cpp.a"
|
||||
"$yaml_install/lib/cmake/yaml-cpp/yaml-cpp-config.cmake"
|
||||
"$eigen_install/include/eigen3/signature_of_eigen3_matrix_library"
|
||||
"$eigen_install/include/eigen3/Eigen/src/Core/util/Macros.h"
|
||||
"$boost_install/include/boost/version.hpp"
|
||||
"$boost_install/lib/libboost_program_options.a"
|
||||
"$boost_install/lib/libboost_regex.a"
|
||||
"$boost_install/lib/libboost_thread.a"
|
||||
"$boost_install/lib/libboost_date_time.a"
|
||||
"$boost_install/lib/libboost_atomic.a"
|
||||
"$zlib_install/include/zlib.h"
|
||||
"$zlib_install/include/zconf.h"
|
||||
"$zlib_install/lib/libz.a"
|
||||
"$xerces_install/lib/libxerces-c.a"
|
||||
"$xerces_install/lib/cmake/XercesC/XercesCConfig.cmake"
|
||||
"$qt_install/lib/libQt5Core.a"
|
||||
"$qt_install/lib/libQt5Network.a"
|
||||
"$qt_install/lib/libQt5Xml.a"
|
||||
"$qt_install/lib/cmake/Qt5/Qt5Config.cmake"
|
||||
"$python_install/include/python3.12/Python.h"
|
||||
"$python_install/lib/libpython3.12.a"
|
||||
"$icu_install/include/unicode/utypes.h"
|
||||
"$icu_install/lib/libicuuc.a"
|
||||
"$icu_install/lib/libicui18n.a"
|
||||
"$icu_install/lib/libicudata.a"
|
||||
"$occt_install/include/opencascade/Standard_Version.hxx"
|
||||
"$occt_install/lib/libTKernel.a"
|
||||
"$boost_install/lib/libboost_program_options.a" \
|
||||
"$boost_install/lib/libboost_regex.a" \
|
||||
"$boost_install/lib/libboost_thread.a" \
|
||||
"$boost_install/lib/libboost_date_time.a" \
|
||||
"$boost_install/lib/libboost_atomic.a"
|
||||
"$occt_install/lib/cmake/opencascade/OpenCASCADEConfig.cmake"
|
||||
)
|
||||
for required_file in "${required_files[@]}"; do
|
||||
if [[ ! -f "$required_file" ]]; then
|
||||
printf 'Missing Web dependency artifact: %s\n' "$required_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
file "$yaml_install/lib/libyaml-cpp.a" \
|
||||
"$zlib_install/lib/libz.a" \
|
||||
"$xerces_install/lib/libxerces-c.a" \
|
||||
"$qt_install/lib/libQt5Core.a" \
|
||||
"$qt_install/lib/libQt5Network.a" \
|
||||
"$qt_install/lib/libQt5Xml.a" \
|
||||
"$python_install/lib/libpython3.12.a" \
|
||||
"$icu_install/lib/libicuuc.a" \
|
||||
"$icu_install/lib/libicui18n.a" \
|
||||
"$icu_install/lib/libicudata.a" \
|
||||
"$occt_install/lib/libTKernel.a"
|
||||
printf 'Web dependency installations are ready under %s\n' "$build_root"
|
||||
37
tools/profile/perftest.sh
Executable file
37
tools/profile/perftest.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#! /bin/bash
|
||||
|
||||
# Example file to drive the performance profiling python test from the shell.
|
||||
# Built to support the Topological Naming Problem fixes from early 2024;
|
||||
# this will likely need tweaking for your use.
|
||||
|
||||
notnp=<Path to your first executable to profile goes here> #<dir>/bin/FreeCAD${cmd}
|
||||
tnp=<Path to your second executable to profile goes here> #<dir/bin/FreeCAD${cmd}
|
||||
results="results.txt" # File to append measurements to. We do not clear so xargs works on script
|
||||
save="--save" # or "" Whether to save the resulting file from the tnp executable for further testing.
|
||||
|
||||
perf record -o "$1.perf" $notnp -t TestPerf --pass "$@"
|
||||
perf record -o "$1.tnp.perf" $tnp -t TestPerf --pass "$save" "$@"
|
||||
|
||||
# For interactive walking of the details using perf:
|
||||
#perf report -i $1.perf
|
||||
|
||||
# After the two test runs above, process the resulting files to get the numbers.
|
||||
# For perf, use the 'script' command to pull the info out of the file, and then do a little
|
||||
# old school unix manipulation
|
||||
times=($(perf script -F time -i "$1.perf" | sed -e's/://' -e'1p;$!d')) # first, last timestamps
|
||||
totaltime=$(echo ${times[1]} - ${times[0]} | bc)
|
||||
memory=$(grep .size: "$1.mprofile" | cut -f2 -d\ )
|
||||
memory2=$(perf script --header -i "$1.perf" | grep "data size" | cut -f2 -d:)
|
||||
|
||||
timestnp=($(perf script -F time -i "$1.tnp.perf" | sed -e's/://' -e'1p;$!d')) # first, last times
|
||||
totaltimetnp=$(echo ${timestnp[1]} - ${timestnp[0]} | bc)
|
||||
memorytnp=$(grep .size: "$1.tnp.mprofile" | cut -f2 -d\ )
|
||||
memory2tnp=$(perf script --header -i "$1.tnp.perf" | grep "data size" | cut -f2 -d:)
|
||||
|
||||
# To calculate in this script instead of externally, you could do something like this:
|
||||
# delta=$(echo ${totaltimetnp} - ${totaltime} | bc)
|
||||
# percent=$(echo "scale=6; ${delta} / ${totaltime} * 100" | bc)
|
||||
|
||||
# Summarize the run of one document into a CSV line suitable for importing into a spreadsheet.
|
||||
echo $totaltime,$totaltimetnp,$memory,$memorytnp,$memory2,$memory2tnp,"$1" >>"${results}"
|
||||
|
||||
Reference in New Issue
Block a user