You are here

Programming

Building a continuous-integration Android build server on FreeBSD: Part three: configuring Jenkins for on-demand builds

Not so frequently asked questions and stuff: 

The FreeBSD logoImageImage

In this series of blog posts, we're going to create a Android build server for continuous integration on FreeBSD.

  • Part one will explain how to build Android APKs using Gradle on FreeBSD using the Linux emulation.
  • Part two will explain how to configure Gitlab-CI to be able to run builds automatically for each commit.
  • Part three (this post) will explain how to configure Jenkins to be able to run builds and email the APKs to people.

Requirements

We want people from our project to be able to build APKs of a Android app and get them by email once they're built. People should be able to configure their app with some Jenkins parameters.

starting_a_jenkins_build_to_build_an_android_app_on_a_remote_freebsd_server

This post does not explain the basics of Jenkins. You should read the documentation if it's your first time using it.

You will need the following plugins:

Creating the node

In this scenario, I'm going to assume our FreeBSD build server is not on the same host as the Jenkins.

In consequence, we need to link the two systems by installing a Jenkins slave-node on the build system.

Preparing the server

On your Android build system, create a new user:

# pw group add -n jenkins-slave
# pw user add -n jenkins-slave -g jenkins-slave

Create a SSH key pair:

# ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/jenkins-slave/.ssh/id_rsa): jenkins-slave-id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in jenkins-slave-id_rsa.
Your public key has been saved in jenkins-slave-id_rsa.pub.

Add the public key to your SSH's authorized_keys. That will be how Jenkins connect to the slave.

# mkdir ~/.ssh/
# cat jenkins-slave-id_rsa.pub >> ~/.ssh/authorized_keys

Create the node on Jenkins

Go to "Manage Jenkins" -> "Manage credentials", and add a new "SSH Username with private key".
Put the private key you generated earlier there and configure the rest.

You used SSH keys, don't you?

Go to "Manage Jenkins" -> "Manage Nodes" and create a new node.

Configure the node with the right host, port and home directory. Set the location of the java executable, or Jenkins will only try to find it in /bin or /usr/bin and it won't work.
That won't connect. Did you forget about your firewall?

If everything goes well, the master node should connect to the server, install its JAR and start the slave node.

Nodes! More nodes!

# ls
jenkins-slave-pepper-id_rsa     jenkins-slave-pepper-id_rsa.pub slave.jar                       workspace

# ps -U jenkins-slave
  PID TT  STAT     TIME COMMAND
66474  -  IJ    0:08.67 sshd: jenkins-slave@notty (sshd)
66478  -  IsJ  11:51.54 /usr/local/openjdk8/bin/java -jar slave.jar

Configuring the job

Create a new job, give it a nice name, and start the configuration.

The parameters

Click on "This build is parameterized", and add as many parameters as you want. You will be able to use these parameters as Jenkins variable everywhere later in the build.

I have another one with, like, 10 different parameters

Here I have two parameters:

  • One to specify what kind of build I want: debug, staging, production, etc.
  • One that specify where I want to APK to be sent once it's built.

Fetching the code

Configure Jenkins to fetch the source code of the app where it's located.

Gimme the code!
Here I'll fetch it from git.

Building the app

Add the required environment variables to the build.

This is a good environment, don't you think?

Add a gradle build script, and invoke the tasks to build your app. Here you can use the parameters you set up at the beginning of the config.

You can build without gradle if you want

You can build without the gradle plugin if you want. It only displays the build nicer but is strictly non essential.

Email the APKs once they're built

Add a post build step: "Editable Email Notification".

Put the recipient chosen by the user from the parameters into the list of recipients, customize the sender, reply-to, subject, content, etc.

Add the APK files as attachments.

Don't forget to configure the triggers to send the email if the build succeeds. By default, emails are only sent on failure.

Such a complicated configuration

Testing

Start a build. If everything goes well, you should receive the resulting APK by email a few seconds after the build is done.

jenkins_build_is_successfull_and_android_apk_was_build_and_sent_by_email

Building a continuous-integration Android build server on FreeBSD: Part two: configuring Gitlab-CI

Not so frequently asked questions and stuff: 

The FreeBSD logoImageImageImage

In this series of blog posts, we're going to create a Android build server for continuous integration on FreeBSD.

  • Part one will explain how to build Android APKs using Gradle on FreeBSD using the Linux emulation.
  • Part two (this post) will explain how to configure Gitlab-CI to be able to run builds automatically for each commit.
  • Part three will explain how to configure Jenkins to be able to run builds and email the APKs to people.

Step 1: Install, configure and register the Gitlab-CI runner

Most of the information of that step comes from the original documentation.

Create a nice user for the runner:

# pw group add -n gitlab-runner
# pw user add -n gitlab-runner -g gitlab-runner -s /usr/local/bin/bash
# mkdir /home/gitlab-runner
# chown gitlab-runner:gitlab-runner /home/gitlab-runner

Download the runner executable (or build it yourself (see the end of the post)), and mark it as such:

# fetch -o /usr/local/bin/gitlab-ci-multi-runner https://gitlab-ci-multi-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-ci-multi-runner-freebsd-amd64
# chmod +x /usr/local/bin/gitlab-ci-multi-runner

Create and chmod the log file:

# touch /var/log/gitlab_runner.log && sudo chown gitlab-runner:gitlab-runner /var/log/gitlab_runner.log

Create the rc-script (/usr/local/etc/rc.d/gitlab_runner):

#!/bin/sh
# PROVIDE: gitlab_runner
# REQUIRE: DAEMON NETWORKING
# BEFORE:
# KEYWORD:

. /etc/rc.subr

name="gitlab_runner"
rcvar="gitlab_runner_enable"

load_rc_config $name

user="gitlab-runner"
user_home="/home/gitlab-runner"
command="/usr/local/bin/gitlab-ci-multi-runner run"
pidfile="/var/run/${name}.pid"

start_cmd="gitlab_runner_start"
stop_cmd="gitlab_runner_stop"
status_cmd="gitlab_runner_status"

gitlab_runner_start()
{
    export USER=${user}
    export HOME=${user_home}
    if checkyesno ${rcvar}; then
        cd ${user_home}
        /usr/sbin/daemon -u ${user} -p ${pidfile} ${command} > /var/log/gitlab_runner.log 2>&1
    fi
}

gitlab_runner_stop()
{
    if [ -f ${pidfile} ]; then
        kill `cat ${pidfile}`
    fi
}

gitlab_runner_status()
{
    if [ ! -f ${pidfile} ] || kill -0 `cat ${pidfile}`; then
        echo "Service ${name} is not running."
    else
        echo "${name} appears to be running."
    fi
}

run_rc_command $1

Set the rc script as being executable:

chmod +x /usr/local/etc/rc.d/gitlab_runner

Register the runner with Gitlab-CI:

# sudo -u gitlab-runner -H /usr/local/bin/gitlab-ci-multi-runner register

Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/ci):
https://gitlab.example.com/ci

Please enter the gitlab-ci token for this runner:
sqzGW7_PgmarZ3FsSsUa

Please enter the gitlab-ci description for this runner:
[androidbuild]: Example Android build machine

Please enter the gitlab-ci tags for this runner (comma separated):
android
Registering runner... succeeded                     runner=sqzGW7_T

Please enter the executor: docker-ssh+machine, docker, docker-ssh, parallels, shell, ssh, virtualbox, docker+machine:
shell

Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

Enable the runner service and start it:

# sysrc -f /etc/rc.conf "gitlab_runner_enable=YES" 
# service gitlab_runner start

Step 2: add the CI configuration to your Android project

Create a file named .gitlab-ci.yml and configure it to your needs.

before_script:
  - export LD_LIBRARY_PATH="/home/androidbuild/android-sdk-linux/build-tools/23.0.2/lib/"
  - export ANDROID_HOME="/home/androidbuild/android-sdk-linux/"

prod:
  only:
    - master
  script:
    - ./gradlew assembleRelease
  artifacts:
    paths:
      - Example/build/outputs/
    expire_in: 1 week

This example file will trigger a build when branch master is updaded, and it will save the artifacts for one week.

Image

Bonus: compiling the runner ourselves

If there's something I don't like, it's downloading executables from the internet when I can build them myself.
Let's build that Gitlab runner ourselves.

Install golang:

# make -C /usr/ports/lang/go install clean

Create a working go directory structure:

% mkdir -p ~/gowork/src/gitlab.com/gitlab-org/
% cd ~/gowork/src/gitlab.com/gitlab-org/
% setenv GOPATH ~/gowork
% setenv PATH "${PATH}:${GOPATH}/bin"

Get the code from Gitlab (checkout the version you want to build):

% git clone https://gitlab.com/gitlab-org/gitlab-ci-multi-runner.git
% cd gitlab-ci-multi-runner/
% git checkout tags/v1.5.2

Build:

% gmake deps docker
% gmake build_simple
% file "out/binaries/gitlab-ci-multi-runner"
out/binaries/gitlab-ci-multi-runner: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), statically linked, not stripped

Install (as root):

# install -m 755 out/binaries/gitlab-ci-multi-runner /usr/local/bin/gitlab-ci-multi-runner

Done.

Building a continuous-integration Android build server on FreeBSD: Part one: building APKs using Linux emulation

Not so frequently asked questions and stuff: 

ImageThe FreeBSD logo

In this series of blog posts, we're going to create a Android build server for continuous integration on FreeBSD.

  • Part one (this post) will explain how to build Android APKs using Gradle on FreeBSD using the Linux emulation.
  • Part two will explain how to configure Gitlab-CI to be able to run builds automatically for each commit.
  • Part three will explain how to configure Jenkins to be able to run builds and email the APKs to people.

I'll be using a normal 10.2-RELEASE:

# uname -a
FreeBSD androidbuild 10.2-RELEASE-p11 FreeBSD 10.2-RELEASE-p11 #0: Wed Jan 27 15:56:01 CET 2016     root@androidbuild:/usr/obj/usr/src/sys/GENERIC  amd64

Parts of this posts are duplicate from my old post on how to build APKs on FreeBSD.

Step 1: installing the system dependencies

We're going to install many packages, including a lot of GNU-style one. So if you care about having your systems cleans, I suggest your do all this in a jail.

Install gradle:

# make -C /usr/ports/devel/gradle/ install clean

Go drink the longest coffee you can brew. That stuff installs a lot of dependencies.

Install bash and link it to /bin/bash:

# make -C /usr/ports/shells/bash install clean
# ln -s /usr/local/bin/bash /bin/bash

Install git:

# make -C /usr/ports/devel/git install clean

Install python:

# make -C /usr/ports/lang/python install clean

Create a nice user to execute the builds:

# pw useradd androidbuild
# mkdir /home/androidbuild
# chown androidbuild:androidbuild /home/androidbuild

Load FreeBSD's Linux emulation system and install a base CentOS 6 system:

# kldload linux
# cd /usr/ports/emulators/linux_base-c6 && make install distclean

The latest version of the build tools (24.0.0) use 64bit binaries and libraries. If you want to be able to build your apks with them, you'll also need the 64bit Linux emulation. In that case, you must be running a FreeBSD version >= 10.3. In that case, you should do instead:

# kldload linux
# kldload linux64
echo "OVERRIDE_LINUX_BASE_PORT=c6_64" >> /etc/make.conf
echo "OVERRIDE_LINUX_NONBASE_PORTS=c6_64" >> /etc/make.conf
# cd /usr/ports/emulators/linux_base-c6 && make install distclean

If you alread had the 32 bit CentOS base, uninstall the port, remove the /compat/linux files and reinstall the port again after setting the two entries in make.conf.

Step 2: setting-up the Android SDK

Download the SDK

Fetch and extract the latest version of the Linux SDK:

% fetch 'https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz'
% tar xzf android-sdk_r24.4.1-linux.tgz
% setenv ANDROID_HOME /home/androidbuild/android-sdk-linux/

Patch the SDK

As it is now, the SDK will download build tools for Windows, since it obviously won't recognize our FreeBSD.
Since we're going to use the Linux emulation, we need to path the system so that it downloads Linux binaries:

Download the source of the SDK base:

% git clone https://android.googlesource.com/platform/tools/base

Apply the following patch:

diff -r -u a/common/src/main/java/com/android/SdkConstants.java b/common/src/main/java/com/android/SdkConstants.java
--- a/common/src/main/java/com/android/SdkConstants.java        2016-09-06 07:56:56.325948102 +0000
+++ b/common/src/main/java/com/android/SdkConstants.java        2016-09-06 07:58:10.721944140 +0000
@@ -635,6 +635,8 @@
             return PLATFORM_WINDOWS;
         } else if (os.startsWith("Linux")) {                //$NON-NLS-1$
             return PLATFORM_LINUX;
+        } else if (os.startsWith("FreeBSD")) {              //$NON-NLS-1$
+            return PLATFORM_LINUX;
         }

         return PLATFORM_UNKNOWN;
diff -r -u a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java      2016-09-06 07:56:56.828948347 +0000
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java      2016-09-06 07:58:35.160941890 +0000
@@ -216,6 +216,8 @@
             hostOS = HostOs.WINDOWS;
         } else if (os.startsWith("Linux")) {                //$NON-NLS-1$
             hostOS = HostOs.LINUX;
+        } else if (os.startsWith("FreeBSD")) {                //$NON-NLS-1$
+            hostOS = HostOs.LINUX;
         }

         BitSize jvmBits;

Rebuild the patched files.

% javac common/src/main/java/com/android/SdkConstants.java
% javac sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java -cp "sdklib/src/main/java:common/src/main/java:annotations/src/main/java"

Replace the files inside the jar.

% cd sdklib/src/main/java/ && jar uf ${ANDROID_HOME}/tools/lib/sdklib.jar com/android/sdklib/internal/repository/archives/ArchFilter.class
% cd common/src/main/java && jar uf ${ANDROID_HOME}/tools/lib/common.jar com/android/SdkConstants.class

See this patch on the Android website: https://android-review.googlesource.com/#/c/100271/

Download the SDK packages and set-up the build tools

Go to the tool directory:

% cd ${ANDROID_HOME}/tools/

In the example, we're going to build an APK thats uses API version 23.
Let's download the right packages:

% ./android list sdk -u -a
[...]
7- Android SDK Build-tools, revision 23.0.3
31- SDK Platform Android 6.0, API 23, revision 3
119- Google APIs, Android API 23, revision 1
% ./android update sdk -u -a -t tools,platform-tools,7,31,119

Let's find the Linux binaries in the build tools, and brand them as such:

% find build-tools/23.0.3/ -maxdepth 1 -type f -print0 | xargs -0 -n 10 file | grep "ELF"
build-tools/23.0.3/mipsel-linux-android-ld:  ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.8, stripped
build-tools/23.0.3/arm-linux-androideabi-ld: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.8, stripped
build-tools/23.0.3/llvm-rs-cc:               ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=8a4ffbc0e197147c4e968722e995605e1d06ea88, not stripped
build-tools/23.0.3/i686-linux-android-ld:    ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.8, stripped
build-tools/23.0.3/bcc_compat:               ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=d565d03b7bafd03d335bdd246832bb31c7cca527, not stripped
build-tools/23.0.3/aapt:                     ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=cfb63b4ad11d0c2d59f10329f0116706e99bf72e, not stripped
build-tools/23.0.3/aidl:                     ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=3cbd3d742040d61877a3c6544778bf4701b2f62d, not stripped
build-tools/23.0.3/aarch64-linux-android-ld: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.8, stripped
build-tools/23.0.3/split-select:             ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=b3bfb153d0ffaef6ed84c316ff682381ba8d75b2, not stripped
build-tools/23.0.3/dexdump:                  ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=a678a8163a2483107d285ffdc94fdde0d4fb2178, not stripped
build-tools/23.0.3/zipalign:                 ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=7d64216663df9fd3b4048952d095fbd07cb4284f, not stripped

% find build-tools/24.0.3/ -maxdepth 1 -type f -print0 | xargs -0 -n 10 file | grep "ELF" | awk 'BEGIN { FS = ":" } ; {print $1}' | xargs brandelf -t Linux
% find build-tools/24.0.3/ -maxdepth 1 -type f -print0 | xargs -0 -n 10 file | grep "ELF" | awk 'BEGIN { FS = ":" } ; {print $1}' | xargs chmod +x

The SDK is now configured and we should be able to build our apps there.

Step 3: building the APK

Fetch the sources of your app:

% git clone 'https://git.example.org/android-app'
% cd android-app

Be sure to have set environment variable ANDROID_HOME to the SDK location.
Start gradle and see if it complains.

% gradle tasks
------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Android tasks
-------------
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for each variant.
sourceSets - Prints out all the source sets defined in this project.
[...]
To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>

BUILD SUCCESSFUL

Total time: 14.839 secs

Gradle is happy, let's try a build:

% gradle assembleRelease
[...]
BUILD SUCCESSFUL

Total time: 2 mins 24.784 secs

Let's look at the build output:

% ls Truc/build/outputs/apk/
truc-app-release-unaligned.apk   truc-app-release.apk

Success!

How to advertise a device on the network using Python

Not so frequently asked questions and stuff: 

Python logoImage

Situation

You built some IoT device running a Python compliant OS. You want to be able to plug it anywhere, and be able to find it in the network view of Windows (or the equivalent thing of other systems).

Solution: SSDP/UPnP

I've created some code that implements a SSDP server and a HTTP server, in order to notify the network that a device is here.

The base of this code was the SSDP module of coherence.
I took it, converted it to Python 3, and kept only the interesting parts for this project: the parts that responds to `MSEARCH` queries.

Image

The code is attached to this post, and is available as a mirror on Github.

Technical details

First, a SSDP client will try to search devices on the network by issuing a MSEARCH * query:

    M-SEARCH * HTTP/1.1
    HOST:239.255.255.250:1900
    ST:upnp:rootdevice
    MX:2
    MAN:"ssdp:discover"

lib/ssdp.py will handle it and reply with a 200:

    HTTP/1.1 200 OK
    SERVER: ZeWaren example SSDP Server
    LOCATION: http://192.168.4.207:8088/jambon-3000.xml
    USN: uuid:e427ce1a-3e80-43d0-ad6f-89ec42e46363::upnp:rootdevice
    CACHE-CONTROL: max-age=1800
    EXT: 
    last-seen: 1477147409.432466
    ST: upnp:rootdevice
    DATE: Sat, 22 Oct 2016 14:44:26 GMT

The client will then fetch the device description:

    GET /jambon-3000.xml HTTP/1.1
    Cache-Control: no-cache
    Connection: Keep-Alive
    Pragma: no-cache
    Accept: text/xml, application/xml
    User-Agent: FDSSDP
    Host: 192.168.4.207:8088

And lib/upnp_http_server.py will build and serve that file:

HTTP/1.0 200 OK
Server: BaseHTTP/0.6 Python/3.4.3
Date: Sat, 22 Oct 2016 14:44:26 GMT
Content-type: application/xml

<root>
    <specVersion>
        <major>1</major>
        <minor>0</minor>
    </specVersion>
    <device>
        <deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>
        <friendlyName>Jambon 3000</friendlyName>
        <manufacturer>Boucherie numrique SAS</manufacturer>
        <manufacturerURL>http://www.boucherie.example.com/</manufacturerURL>
        <modelDescription>Jambon Appliance 3000</modelDescription>
        <modelName>Jambon</modelName>
        <modelNumber>3000</modelNumber>
        <modelURL>http://www.boucherie.example.com/en/prducts/jambon-3000/</modelURL>
        <serialNumber>JBN425133</serialNumber>
        <UDN>uuid:e427ce1a-3e80-43d0-ad6f-89ec42e46363</UDN>
        <serviceList>
            <service>
                <URLBase>http://xxx.yyy.zzz.aaaa:5000</URLBase>
                <serviceType>urn:boucherie.example.com:service:Jambon:1</serviceType>
                <serviceId>urn:boucherie.example.com:serviceId:Jambon</serviceId>
                <controlURL>/jambon</controlURL>
                <eventSubURL/>
                <SCPDURL>/boucherie_wsd.xml</SCPDURL>
            </service>
        </serviceList>
        <presentationURL>http://192.168.4.207:5000/</presentationURL>
    </device>
</root>

The client now knows (nearly) everything about the device.

Relevant links

How to debug python code using GDB on FreeBSD without compromising your system

Not so frequently asked questions and stuff: 

GDB's logoImageThe FreeBSD logo

Introduction

We want to be able to use GDB to debug python code efficiently.

Let's say we have the following code:

from threading import Event
import random
from time import sleep

def blocking_function_one():
	while True:
		sleep(1)

def blocking_function_two():
	e = Event()
	e.wait()

if random.random() > 0.5:
	blocking_function_one()
else:
	blocking_function_two()

That code will block, and since it doesn't output anything, we have no way of knowing if we went into blocking_function_one or blocking_function_two. Or do we?

For reference, I'm running a 10.2-RELEASE:

# uname -a
FreeBSD bsdlab 10.2-RELEASE FreeBSD 10.2-RELEASE #0 r286666: Wed Aug 12 15:26:37 UTC 2015     root@releng1.nyi.freebsd.org:/usr/obj/usr/src/sys/GENERIC  amd64

Step 1: installing a debug version of python

We're going to work in a separate directory, in order not to alter our installation. If we need this in production, we want to be able to let the system as clean as possible when we leave.

# mkdir /usr/local/python-debug

Build python 3.4 from the port collection and set it to be installed in the directory we just created:

# cd /usr/ports/lang/python34
# make install PREFIX=/usr/local/python-debug OPTIONS_FILE_SET+=DEBUG BATCH=1

Normally we would have used NO_PKG_REGISTER=1 to install the package without registering it on the system. Unfortunately, this option is not working anymore (see bug https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=182347).

So, let's copy the files ourselves:

# cp -r work/stage/usr/local/python-debug/* /usr/local/python-debug/

Let's try to run that new python installation:

# setenv LD_LIBRARY_PATH /usr/local/python-debug/lib
# /usr/local/python-debug/bin/python3.4dm
Python 3.4.5 (default, Sep  4 2016, 00:42:59)
[GCC 4.2.1 Compatible FreeBSD Clang 3.4.1 (tags/RELEASE_34/dot1-final 208032)] on freebsd10
Type "help", "copyright", "credits" or "license" for more information.
>>>

The "d" in "dm" means "debug".

Building python also produced an important file that we need to save for later before cleaning the work tree:

# cp work/Python-3.4.5/python-gdb.py ~/

Step 2: building a python aware GDB

Build GDB from the port collection:

  • Making sure the python extensions are enabled
  • Telling the configure script where to find our python installation
  • Telling the configure and build scripts where to find the relevant headers and libraries
# ln -s python3.4 /usr/local/python-debug/bin/python
# cd /usr/ports/devel/gdb
# make PREFIX=/usr/local/python-debug \
OPTIONS_FILE_SET+=PYTHON \
PYTHON_CMD=/usr/local/python-debug/bin \
BATCH=1 \
CFLAGS+="-I/usr/local/python-debug/include -L/usr/local/python-debug/lib" \
CXXFLAGS+="-I/usr/local/python-debug/include -L/usr/local/python-debug/lib"

Also copy that installation manually to our special directory:

# cp -r work/stage/usr/local/python-debug/* /usr/local/python-debug/

Let's check that it's working and has the python extensions:

# /usr/local/python-debug/bin/gdb
GNU gdb (GDB) 7.11.1 [GDB v7.11.1 for FreeBSD]
[...]
(gdb) python
>import gdb
>end
(gdb)

Step 3: wire it all together

Now we have:

  • A version of Python that integrates debug information.
  • A version of GDB that can run higher level GDB scripts written in Python.
  • A python-gdb script to add commands and macros.

Copy the GDB script somewhere where Python can load it:

# mkdir ~/.python_lib
# mv ~/python-gdb.py ~/.python_lib/python34_gdb.py

Let's run our stupid blocking script:

# setenv PATH "/usr/local/python-debug/bin/:${PATH}"
# python where-am-i-blocking.py
[blocked]

In another shell, find the PID of the script, and attach GDB there.

# ps auxw | grep python
root     24226   0.0  0.7  48664  15492  3  I+    3:00AM     0:00.13 python where-am-i-blocking.py (python3.4)
root     24235   0.0  0.1  18824   2004  4  S+    3:00AM     0:00.00 grep python

# setenv PATH "/usr/local/python-debug/bin/:${PATH}"
# gdb python 24226
GNU gdb (GDB) 7.11.1 [GDB v7.11.1 for FreeBSD]
[...]
[Switching to LWP 100160 of process 24226]
0x00000008018e3f18 in _umtx_op () from /lib/libc.so.7
(gdb)

Load the GDB python script:

(gdb) python
>import sys
>sys.path.append('/root/.python_lib')
>import python34_gdb
>end

The python macros are now loaded:

(gdb) py
py-bt               py-down             py-locals           py-up               python-interactive
py-bt-full          py-list             py-print            python

Let's see where we are:

(gdb) py-bt
Traceback (most recent call first):
  <built-in method acquire of _thread.lock object at remote 0x80075c2a8>
  File "/usr/local/python-debug/lib/python3.4/threading.py", line 290, in wait
    waiter.acquire()
  File "/usr/local/python-debug/lib/python3.4/threading.py", line 546, in wait
    signaled = self._cond.wait(timeout)
  File "where-am-i-blocking.py", line 11, in blocking_function_two
    e.wait()
  File "where-am-i-blocking.py", line 16, in <module>
    blocking_function_two()

We're in blocking_function_two.

Let's check the wait's frame local variables:

(gdb) bt
#0  0x00000008018e3f18 in _umtx_op () from /lib/libc.so.7
#1  0x00000008018d3604 in sem_timedwait () from /lib/libc.so.7
#2  0x0000000800eb0421 in PyThread_acquire_lock_timed (lock=0x802417590, microseconds=-1, intr_flag=1) at Python/thread_pthread.h:352
#3  0x0000000800eba84f in acquire_timed (lock=0x802417590, microseconds=-1) at ./Modules/_threadmodule.c:71
#4  0x0000000800ebab82 in lock_PyThread_acquire_lock (self=0x80075c2a8, args=(), kwds=0x0) at ./Modules/_threadmodule.c:139
#5  0x0000000800cfa963 in PyCFunction_Call (func=<built-in method acquire of _thread.lock object at remote 0x80075c2a8>, arg=(), kw=0x0)
    at Objects/methodobject.c:99
#6  0x0000000800e31716 in call_function (pp_stack=0x7fffffff5a00, oparg=0) at Python/ceval.c:4237
#7  0x0000000800e29fc0 in PyEval_EvalFrameEx (
    f=Frame 0x80245d738, for file /usr/local/python-debug/lib/python3.4/threading.py, line 290, in wait (self=<Condition(_lock=<_thread.l
ock at remote 0x80075c510>, _waiters=<collections.deque at remote 0x800b0e9d8>, release=<built-in method release of _thread.lock object a
t remote 0x80075c510>, acquire=<built-in method acquire of _thread.lock object at remote 0x80075c510>) at remote 0x800a6fc88>, timeout=No
ne, waiter=<_thread.lock at remote 0x80075c2a8>, saved_state=None, gotit=False), throwflag=0) at Python/ceval.c:2838
[...]
#25 0x0000000800eb4e86 in run_file (fp=0x801c1e140, filename=0x802418090 L"where-am-i-blocking.py", p_cf=0x7fffffffe978)
    at Modules/main.c:319
#26 0x0000000800eb3ab7 in Py_Main (argc=2, argv=0x802416090) at Modules/main.c:751
#27 0x0000000000400cae in main (argc=2, argv=0x7fffffffeaa8) at ./Modules/python.c:69

(gdb) frame 7
#7  0x0000000800e29fc0 in PyEval_EvalFrameEx (
    f=Frame 0x80245d738, for file /usr/local/python-debug/lib/python3.4/threading.py, line 290, in wait (self=<Condition(_lock=<_thread.lock at remote 0x80075c510>, _waiters=<collections.deque at remote 0x800b0e9d8>, release=<built-in method release of _thread.lock object at remote 0x80075c510>, acquire=<built-in method acquire of _thread.lock object at remote 0x80075c510>) at remote 0x800a6fc88>, timeout=None, waiter=<_thread.lock at remote 0x80075c2a8>, saved_state=None, gotit=False), throwflag=0) at Python/ceval.c:2838
2838                res = call_function(&sp, oparg);

(gdb) py-locals
self = <Condition(_lock=<_thread.lock at remote 0x80075c510>, _waiters=<collections.deque at remote 0x800b0e9d8>, release=<built-in method release of _thread.lock object at remote 0x80075c510>, acquire=<built-in method acquire of _thread.lock object at remote 0x80075c510>) at remote 0x800a6fc88>
timeout = None
waiter = <_thread.lock at remote 0x80075c2a8>
saved_state = None
gotit = False

If you don't want to or can't attach to the running process, you can do the same thing with a core dump:

# gcore 24226
# gdb python core.24226

If you don't want to add the lib directory to the path of python everytime you use GDB, add it to your profile's GDB init script:

cat > ~/.gdbinit <<EOF
python
import sys
sys.path.append('/root/.python_lib')
end
EOF

You'll only need to import the module (python import python34_gdb) and you'll be good to go.

More ressources

Bonus problem: loading Debian's libc's debug info on a armhf

I've done the exact same thing on a Beagle Bone Black system running Debian.

Unfortunately GDB was complaining that the stack was corrupt.

# gdb /usr/local/opt/python-3.4.4/bin/python3.4dm core.18513
GNU gdb (GDB) 7.11
[...]
Reading symbols from /usr/local/opt/python-3.4.4/bin/python3.4dm...done.
[New LWP 18513]
[New LWP 18531]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/arm-linux-gnueabihf/libthread_db.so.1".
Core was generated by `python3.4dm'.
#0  0xb6f7d7e0 in recv () from /lib/arm-linux-gnueabihf/libpthread.so.0
[Current thread is 1 (Thread 0xb6fac000 (LWP 18513))]
(gdb) bt
#0  0xb6f7d7e0 in recv () from /lib/arm-linux-gnueabihf/libpthread.so.0
#1  0xb6f7d7d4 in recv () from /lib/arm-linux-gnueabihf/libpthread.so.0
#2  0xb64ae6f8 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb)

A search on the internet indicated that it was because I was missing package libc6-dbg, but that package was installed.

# apt-get install libc6-dbg
Reading package lists... Done
Building dependency tree
Reading state information... Done
libc6-dbg is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

The problem was that using my custom installation directory had GDB look for these files in the wrong place.

(gdb) show debug-file-directory
The directory where separate debug symbols are searched for is "/usr/local/opt/python-3.4.4/lib/debug".

Setting that variable in the init file solves the problem:

cat >> ~/.gdbinit <<EOF
set debug-file-directory /usr/lib/debug
EOF
[Current thread is 1 (Thread 0xb6fac000 (LWP 18513))]
(gdb) bt
#0  0xb6f7d7e0 in recv () at ../sysdeps/unix/syscall-template.S:82
#1  0xb68fe18e in sock_recv_guts (s=0xb64ae6f8, cbuf=0x50e2e8 '\313' <repeats 199 times>, <incomplete sequence \313>..., len=65536, flags=0)
    at /tmp/Python-3.4.4/Modules/socketmodule.c:2600
[...]
#38 0x00025752 in PyRun_AnyFileExFlags (fp=0x32bcc8, filename=0xb6be5310 "main.py", closeit=1, flags=0xbed1db20) at Python/pythonrun.c:1287
#39 0x0003b1ee in run_file (fp=0x32bcc8, filename=0x2c99f0 L"main.py", p_cf=0xbed1db20) at Modules/main.c:319
#40 0x0003beb8 in Py_Main (argc=2, argv=0x2c9010) at Modules/main.c:751
#41 0x000208d8 in main (argc=2, argv=0xbed1dd14) at ./Modules/python.c:69

How to install PyInstaller in a Python Virtual-Env on FreeBSD

Not so frequently asked questions and stuff: 

ImageThe FreeBSD logo

# uname -a
FreeBSD freebsderlang 10.3-RELEASE FreeBSD 10.3-RELEASE #0 r297264: Fri Mar 25 02:10:02 UTC 2016     root@releng1.nyi.freebsd.org:/usr/obj/usr/src/sys/GENERIC  amd64

Install Python and VirtualEnv

Install Python:

# make -C /usr/ports/lang/python34/ install clean

Install virtualenv:

# setenv PYTHON_VERSION python3.4
# make -C /usr/ports/devel/py-virtualenv install clean

Create a virtual env:

# virtualenv-3.4 venv
Using base prefix '/usr/local'
New python executable in /root/somewhere/venv/bin/python3.4
Also creating executable in /root/somewhere/venv/bin/python
Installing setuptools, pip, wheel...done.

Dive into it:

# source venv/bin/activate.csh

Download, build and install PyInstaller

Download the latest version of PyInstaller, check that it was correctly downloaded, and extract it:

[venv] # fetch 'https://github.com/pyinstaller/pyinstaller/releases/download/v3.2/PyInstaller-3.2.tar.gz' --no-verify-peer
[venv] # sha256 PyInstaller-3.2.tar.gz
SHA256 (PyInstaller-3.2.tar.gz) = 7598d4c9f5712ba78beb46a857a493b1b93a584ca59944b8e7b6be00bb89cabc
[venv] # tar xzf PyInstaller-3.2.tar.gz

Go into the bootloader directory and build all:

[venv] # cd PyInstaller-3.2/bootloader/
[venv] # python waf all

Go to the release and build and install as usual:

[venv] # cd ..
[venv] # python setup.py install

Test PyInstaller

[venv] # cat > some_python_script.py << EOF
print("Je suis une saucisse")
EOF
[venv] # pyinstaller --onefile some_python_script.py
[venv] # dist/some_python_script
Je suis une saucisse

How to translate a Flask-App, including content generated by Javascript

AttachmentSize
Package icon flask-i18n-example-master.zip24.34 KB
Not so frequently asked questions and stuff: 

ImageImage

It is well known that if you want your Flask apps to support internationalization, you should use The Flask Mega-Tutorial recommends creating a single javascript file per language. While this method is working, I would prefer using a standard gettext style method.

Meet jsgettext. It is basically a boiled down version of the usual gettext tool that is used everywhere in nearly every programming language. It supports the standard gettext functions, including the one using contexts or plural forms.

Unfortunately, I found that the documentation of its usage was quite limited. This is why I uploaded here a sample app that gets its text translated in 3 different parts:

  • In the Python code.
  • In the Jinga templates.
  • In Javascript generated text

ImageImage

Each part include examples of translations including placeholders and/or plural forms.

You can find the code attached to this post, but for convenience I've also uploaded it on GitHub:
https://github.com/ZeWaren/flask-i18n-example

Jabber/XMPP transport for Google Hangouts

Not so frequently asked questions and stuff: 

ImageImage

Social pressure forces me to communicate with people using Hangouts, Google's instant messaging platform.

Unfortunately, the only way to use it is either on Gmail, or using the worst client you've ever seen: a Google Chrome extension.

My operating system is not a f*cking web browser!

What if I want to?:

  • Copy links in chat messages without opening them?
  • Open links in my default web browser and not in Chrome?
  • Use the integrated notification system of my operating system and not some weak-ass "tab title change"?
  • Use IM on slow hardware?

Being an active user of Jabber/XMPP, I decided the best way to solve all these problems at once would be to write a transport for my Jabber server.

This transport can be found on GitHub:
https://github.com/ZeWaren/jabber-hangouts-transport

That way, I can communicate with people using Hangouts with any Jabber client.

Here are a few screenshots from Psi and Pidgin:

ImageImageImageImage

How to use the CAN bus of a Beagle Bone Black from Erlang

Not so frequently asked questions and stuff: 

Communication through a CAN bus between a Beagle Bone Black and a STM32F302 NucleoImage

Let's try using the CAN bus device of a Beagle Bone Black from Erlang.

Here's some information about the system present on the board:

# uname -a
Linux beaglebone 3.14.40-ti-r62 #1 SMP PREEMPT Thu Apr 30 18:28:06 UTC 2015 armv7l GNU/Linux

# cat /etc/issue
Debian GNU/Linux 8 \n \l

BeagleBoard.org Debian Image 2015-05-01

Testing the CAN driver

Let's create a virtual CAN device:

# modprobe vcan
# ip link add dev vcan0 type vcan
# ifconfig vcan0 up
# ip -details -statistics link show vcan0
5: vcan0:  mtu 16 qdisc noqueue state UNKNOWN mode DEFAULT group default
    link/can  promiscuity 0
    vcan
    RX: bytes  packets  errors  dropped overrun mcast
    0          0        0       0       0       0
    TX: bytes  packets  errors  dropped carrier collsns
    0          0        0       0       0       0

Let's try sending/receiving data on that virtual device:

# cansend vcan0 442#DEADBABE

On another terminal:

# candump vcan0
  vcan0  442   [4]  DE AD BA BE

Using the CAN driver from Erlang

I'll be using the excellent CAN module from Tony Rogvall: https://github.com/tonyrog/can.

Let's create a project from the following rebar.config:

{lib_dirs,["deps"]}.
{sub_dirs, ["rel"]}.
{deps, [
    {lager, ".*", {
        git, "git://github.com/basho/lager.git", "master"}
    },
    {can, ".*", {
        git, "git://github.com/tonyrog/can", "master"}
    }
]}.
{erl_opts, [{parse_transform, lager_transform}]}.

Compile everything and start a shell:

# rebar compile
# erl -pa ebin/ -pa deps/*/ebin
1> lager:start().
12:53:30.268 [info] Application lager started on node nonode@nohost
ok
2> lager:set_loglevel(lager_console_backend, debug).
ok
3> can:start().
12:53:39.905 [info] Application uart started on node nonode@nohost
ok
4> can_sock:start(0, [{device,"vcan0"}]).
12:53:39.963 [debug] Supervisor can_sup started can_router:start_link([]) at pid 
12:53:39.965 [debug] Supervisor can_sup started can_if_sup:start_link() at pid 
12:53:39.966 [info] Application can started on node nonode@nohost

12:53:41.213 [debug] can_router: process , param {can_sock,"vcan0",0} joined.
12:53:41.215 [debug] Supervisor can_sup started can_sock:start_link(0, [{device,"vcan0"}]) at pid 
{ok,}

Driver and device are loaded. Let's try sending something:

5> can:send(42, >).
ok
6> 12:54:42.626 [debug] can_router: broadcast: [ID: 02A LEN:4 DATA:41424344]
12:54:42.627 [debug] broadcast: frame={can_frame,42,4,>,0,0}, send=1

On the dump:

# candump vcan0
  vcan0  02A   [4]  41 42 43 44

Sending data works. Let's try receiving data.

Let's attach the shell pid to the router.

6> can_router:attach().
12:58:06.992 [debug] can_router: process  attached.
ok

Information about the module can be queried:

can_router:i().
Interfaces
 1: {can_sock,"vcan0",0}
  input_frames: 3
  output_frames: 2
Applications
:  interface=0
ok

Send something from the terminal:

# cansend vcan0 442#44434241

Do we have a new message?

7> receive V -> V end.
12:58:31.517 [debug] can_router: broadcast: [ID: 442 LEN:4 INTF:1 DATA:44434241]
12:58:31.517 [debug] broadcast: frame={can_frame,1090,4,>,1,-1}, send=1
{can_frame,1090,4,>,1,-1}

Success!

Using the real CAN

Plug your CAN transceiver the following way:

The Beagle Bone Black CAN pinout

Pin Description
24 CAN RX
26 CAN TX

Make sure the dtb is loaded in /boot/uEnv.txt:

dtb=am335x-boneblack-can1.dtb

If not, edit the file and reboot the board.

You can check that the board is aware of its CAN interface:

# cat /sys/kernel/debug/pinctrl/44e10800.pinmux/pinmux-pins | grep -i can
pin 96 (44e10980.0): P9_26_pinmux.53 (GPIO UNCLAIMED) function pinmux_P9_26_can_pin group pinmux_P9_26_can_pin
pin 97 (44e10984.0): P9_24_pinmux.51 (GPIO UNCLAIMED) function pinmux_P9_24_can_pin group pinmux_P9_24_can_pin

Stop the driver, configure the link and start the interface again:

# ifconfig can0 down
# ip link set can0 up type can bitrate 1000000 loopback off triple-sampling on
# if config can0 up

If everything goes right, the link should be working and you can query information about it:

# ip -details -statistics link show can0
3: can0:  mtu 16 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 10
    link/can  promiscuity 0
    can  state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 0
          bitrate 1000000 sample-point 0.750
          tq 83 prop-seg 4 phase-seg1 4 phase-seg2 3 sjw 1
          c_can: tseg1 2..16 tseg2 1..8 sjw 1..4 brp 1..1024 brp-inc 1
          clock 24000000
          re-started bus-errors arbit-lost error-warn error-pass bus-off
          0          0          0          1          1          0
    RX: bytes  packets  errors  dropped overrun mcast
    14058      2425     0       89      0       0
    TX: bytes  packets  errors  dropped carrier collsns
    591        591      0       0       0       0

For information, here's how my other device is configured:

//CLOCK CONFIG:

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);

  __SYSCFG_CLK_ENABLE();

//CAN CONFIG:

  hcan.Instance = CAN;
  hcan.Init.Prescaler = 2;
  hcan.Init.Mode = CAN_MODE_NORMAL;
  hcan.Init.SJW = CAN_SJW_1TQ;
  hcan.Init.BS1 = CAN_BS1_9TQ;
  hcan.Init.BS2 = CAN_BS2_8TQ;
  hcan.Init.TTCM = DISABLE;
  hcan.Init.ABOM = DISABLE;
  hcan.Init.AWUM = DISABLE;
  hcan.Init.NART = DISABLE;
  hcan.Init.RFLM = DISABLE;
  hcan.Init.TXFP = DISABLE;
  HAL_CAN_Init(&hcan);

(it's a STM32F302).

Using Intellij IDEA to write and debug Erlang code

Not so frequently asked questions and stuff: 

ImageImage
Written 2015-02-02.

Requirements

My system is a Windows 8.1 (6.3 build 9600).

Download IntelliJ IDEA from JetBrains (I use the Community Edition, currently ideaIC-14.0.3.exe).

My installed erlang is:

Erlang/OTP 17 [erts-6.2] [64-bit] [smp:8:8] [async-threads:10]

Start IDEA, and go the plugin page (click "Configure"). Install the very nice Erlang plugin (currently ver 0.5.9 (2014-11-18)).

Image

Install rebar somewhere on your system (clone the repository and run bootstrap.bat with the erlang binaries in your PATH).

Configure the IDE

In the IDE's settings window, go to "Other Settings / Erlang External Tools" and input the location of your rebar binaries.

Image

In "Build, Execution, Deployment/Erlan Compiler", check "Compile project with rebar".

Image

Create a new project, and select Erlang. Leave "Additional Libraries and Frameworks" empty and click Next.

Click on "Configure" and input the location of your Erlang SDK (exemple: "C:\Program Files\erl6.2").

If you did well, the SDK is recognized correctly.

Image

Debugging a simple file

Let's create a simple Erlang code and try to debug it.

Add a file, input some code, and click "Build / Make".

If your code is incorrect, the list of warnings and errors appears, and you can double click on the items to go directly to the source of error (like in any good IDE).

Image

If your code is correct, the project is compiled.

A simple default debug configuration is created. You can put a breakpoint in your code and debug it.

ImageImage

So far, so good.

Debugging standard OTP applications

Now let's try to debug something more serious, like an OTP application.

rebarcreate-app appid=testerlangide
==> testerlangide (create-app)
Writing src/testerlangide.app.src
Writing src/testerlangide_app.erl
Writing src/testerlangide_sup.erl

Let's create a gen_server which prints something every 5 seconds

...

-define(DELAY_BETWEEN_LOOKUPS, 5000).

-record(state, {timer}).

init([]) ->
  Timer = erlang:send_after(?DELAY_BETWEEN_LOOKUPS, self(), timer_ticked),
  {ok, #state{timer=Timer}}.

...

handle_info(timer_ticked, #state{timer=OldTimer}=State) ->
  erlang:cancel_timer(OldTimer),
  io:format("Tick!~n"),
  Timer = erlang:send_after(?DELAY_BETWEEN_LOOKUPS, self(), timer_ticked),
  {noreply, State#state{timer=Timer}};

...

I've tried debugging the application directly, by having the debugger directly start the application (application:start(testerlangide)), but it didn't work well:

  • When "Stop Erlang interpreter automatically after execution" is ticked, the Erlang node is stopped directly after the app is started, which is obviously not good.
  • When "Stop Erlang interpreter automatically after execution" is not ticked, the breakpoints in the gen_server do not work, because the node is considered by the IDE to be stopped. Thus you can't stop it, which is also not good.

What I ended up doing was the creation of a "proxy" file, that starts the app, and does nothing until the node is stopped. That way, the breakpoints in the gen_servers work, and the node can be stopped by clicking on the red square button in the IDE.

-export([debug/0]).

loop_sleep() ->
  timer:sleep(5000),
  loop_sleep().

debug() ->
  %% Start your dependencies here
  application:start(testerlangide),
  loop_sleep().

ImageImage

Debugging a remote node

Using Intellij on Windows works fine, but many Erlang libraries don't. In my opinion, Erlang is much more pleasant on UNIX systems. Still, that shouldn't prevent us from debugging.

Fortunately, the author of the plugin included a way to debug remote nodes. A local node will be spawned on the IDE's computer, connect to the target node, and fire up the debugger.

Image

If your remote node is using long names, please be sure that your intellij-erlang include this commit and this commit. Otherwise, your local node won't be started with a correct name and debugging will fail.

Image

You can even connect to an already running system, debug a few lines, and let it live again normally. That's perfect to debug hard live problems.

Pages

Subscribe to Programming