06.01.2018
Продолжаем цикл статей “Как кодить на С++ под OSX для Ubuntu”. После сборки cross-toolchain’ов стояла задача научиться собирать, запускать и отлаживать С++ под OSX для Linux (Ubuntu в данном случае). Как оказалось cross-toolchain был не нужен :(. Но об этом позже.
Для начала нужна была IDE, в которой можно было бы комфортно редактировать код и отлаживаться. Претенденты были такие: Xcode, Eclipse (CDT), CLion, чуть позже к ним добавился VSCode и впоследствии выиграл соревнование.
Постановка задачи была такая: C++, CMake, Linux, при этом редактирование кода и отладка должна работать под OSX.
Xcode поэтому выпал из этой гонки сразу, он есть очень удобная IDE для C++, но в целом в основном для OSX. CMake наверное завести можно, но это будет большое количество сторонних средств.
CLion выпал из этой же гонки примерно по тем же причинам (хотя он вроде умеет в CMake). Плюс я не смог в нем нормально подружиться с Docker.
Остался Eclipse CDT, в нем получилось практически все что требовалось (с cross-toolchain), завелась даже отладка через cross-gdb, однако были странные махинации чтобы нормально работать с CMake и контейнером он управлял относительно сам (не совсем прозрачно), хотя надо отметить что поддержка Docker в Eclipse плюс-минус хорошая.
После этого я пошел посерфить интеренет в поисках каких-нибудь идей, как нормально подружить Eclipse и CMake, и тут я наткнулся на VSCode. Которые не позиционирует себя как IDE, но умеет в дебаг и всякие user-defined task’и. Несмотря на мое некоторое отношение к продуктам Microsoft, я решил дать этому редактору шанс и попробовал его… и надо признать, это очень хороший инструмент.
Теперь же собственно, как завести, чтобы все работало.
Для начала нам понадобится собственно VSCode, настроенный так, как вам наиболее удобно. Можно сразу поставить расширения для работы с C++ (C/C++, Native Debug), CMake (CMake, CMake Tools), Docker.
Далее нам потребуется такая структура проекта (в принципе вы можете выбрать такую структуру, какая вам больше нравится, это скажем так рабочий пример):
.vscode
tasks.json
launch.json
build
CMakeLists.txt
dbuild.sh
ddebugger.sh
dmaker.sh
Dockerfile
main.cpp
Начнем с С++, пример простой, однако чтобы понять что мы действительно работаем для Linux я добавил несколько linux’овых вещей.
#include <unistd.h>
#include <limits.h>
#include <iostream>
using namespace std;
int main() {
char hostname[HOST_NAME_MAX];
char username[LOGIN_NAME_MAX];
gethostname(hostname, HOST_NAME_MAX);
getlogin_r(username, LOGIN_NAME_MAX);
cout << "hostname: " << hostname << ", login: " << username << endl; // prints Hello World!
return 0;
}
Этот код мы будем собирать, запускать и отлаживать.
Далее CMakeLists.txt, который тоже простой и относительно шаблонный:
cmake_minimum_required(VERSION 3.5)
project(HWord)
set(HWORD_VERSION_MAJOR 1)
set(HWORD_VERSION_MINOR 0)
set(SOURCE ${PROJECT_SOURCE_DIR}/main.cpp)
add_executable(${PROJECT_NAME} ${SOURCE})
Собираем
Как я уже выше писал сначала я пытался собрать все это безобразие под OSX с помощью cross-toolchain. Это плюс-минус работало, но часто приходилось пересобирать toolchain с еще каким-нибудь волшебным флагом, а на моем машине это делается примерно 3 часа. В одну из таких пересборок мне пришла мысль, а почему бы не собирать docker’ом, т.е. gcc который в контейнере и в требуемой системе (linux).
Таким образом собираем такой контейнер:
Dockerfile
FROM ubuntu:16.04
RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y gcc g++ gdb cmake
WORKDIR /opt/build
VOLUME ["/opt"]
Для упрощения работы с командной строкой docker’а заведем пару скриптов:
Скрипт сборки образа:
dbuild.sh
#!/bin/bash
cwd=$(pwd)
docker build \
-t mrdekk/maker \
.
Ничего сверхординарного, но возможно вам лучше поставить свой тег, вместо mrdekk/maker
сделать %username%/maker
Скрипт запуска сборок:
dmaker.sh
#!/bin/bash
cwd=$(pwd)
docker stop maker
docker rm maker
docker run \
-it \
--name maker \
-p 6666:6666 \
-v ${cwd}:/opt \
--privileged \
mrdekk/maker \
"${@}"
Скрипт запуска контейнера для отладки:
ddebugger.sh
#!/bin/bash
cwd=$(pwd)
docker stop maker
docker rm maker
docker run \
-dt \
--name maker \
-p 6666:6666 \
-v ${cwd}:/opt \
--privileged \
mrdekk/maker \
"${@}"
Скрипты запуска сборки и дебаггера в общем отличаются только ключами -it и -dt.
Теперь сделаем несколько задач (tasks) для VSCode
Задача сборки контейнера:
Ничего особенного, вызываем скрипт dbuild.sh
{
"label": "Docker: Build Containers",
"command": "${workspaceFolder}/dbuild.sh",
},
И заодно выполняем ее, после этого можете сделать docker images
и проверить, что образ mrdekk/maker появился
Задача для CMake:
Теперь с помощью CMake сгенерим Makefile’ы для сборки проекта. Здесь мы воспользуемся скриптом dmaker.sh, но будем вызывать его с набором ключей:
{
"label": "CMake: Initialize",
"command": "${workspaceFolder}/dmaker.sh",
"args": [
"cmake", "-G", "'Unix Makefiles'", "-DCMAKE_BUILD_TYPE=Debug", "/opt"
],
"options": {
"cwd": "${workspaceFolder}"
}
},
/opt
это путь до Volume’а в контейнере в который мы будем проецировать текущую папку с исходниками проекта - это в общем часть магии.
После этого в подпапке build вы должны увидеть сгенерированные cmake’ом файлы.
Задача для сборки бинарника:
Здесь снова используем скрипт dmaker.sh но с другими ключами
{
"label": "Make: Build Project",
"command": "${workspaceFolder}/dmaker.sh",
"args": [
"make", "-j", "8"
],
"options": {
"cwd": "${workspaceFolder}"
},
"group": {
"kind": "build",
"isDefault": true
}
},
После того, как собереться можете попробовать сделать так ./build/HWord
на OSX, и вы должны получить примерно такое:
exec format error: ./build/HWord
Это правильно, наш бинарник собран для linux и на OSX запускаться не должен. Если же вы увидите вывод программы, это значит что что-то пошло не так (вы собрали OSX’овым компилятором).
Запускаем без отладки
Теперь у нас есть бинарник, надо проверить что он запускается. Для этого снова воспользуемся скриптом dmaker.sh и такой задачей:
{
"label": "Docker: Run Containers",
"command": "${workspaceFolder}/dmaker.sh",
"args": [
"/opt/build/HWord"
]
},
После этого вы должны увидеть в docker logs maker
и терминале VSCode примерно такое
hostname: 03a3a4dee13f, login:
Это хорошо, наш бинарник на linux работает хорошо.
Отлаживаемся
С помощью VSCode поставьте breakpoint куда-нибудь в коде. Нам будет необходим запущенный в фоне контейнер, внутри которого мы через docker exec
будем запускать gdb. Увы, завести VSCode так, чтобы он смог нормально в интерактивном режиме (dmaker.sh и -it) запускать debugger у меня не получилось (видимо механизм запуска дебаггера в VSCode как-то так хитро устроен), поэтому я воспользовался pipeTransport и все заработало.
Таким образом задача для предварительного запуска контейнера:
{
"label": "Docker: Run Debugging Container",
"command": "${workspaceFolder}/ddebugger.sh",
"args": [
"bash"
]
}
а launch.json выглядит так
{
"version": "0.2.0",
"configurations": [
{
"name": "Container Debug",
"type": "cppdbg",
"request": "launch",
"program": "/opt/build/HWord",
"cwd": "/opt",
"linux": {
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
"osx": {
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
// "preLaunchTask": "Docker: Run Debugging Container",
"pipeTransport": {
"pipeProgram": "docker",
"pipeCwd": "${workspaceRoot}",
"pipeArgs": [
"exec", "-i", "maker", "sh", "-c"
],
"quoteArgs": false,
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
"/opt":"${workspaceFolder}"
},
}
]
}
Внимательно ко всем параметрам, они все имеют значение.
После этого жмем F5 (или в меню выбираем Start Debugging) и… отлаживаемся. Дебаггер должен остановить выполнение в установленном вами breakpoint’е.
Примеры кода и исходники к статье лежат тут mrdekk/viscose-cpp-docker.
P.S. Да, я тоже ненавижу автокомплит, когда он что-то исправляет. В названии репозитория должно было быть vscode-cpp-docker, а получилось viscose-cpp-docker. Но, ладно! Так тому и быть.
01.01.2018
Для начала нам необходимо crosstool-ng для сборки toolchain’а для компиляции под Linux на базе OSX.
Case Sensitive Volume
Для установки crosstool-ng и для сборки правильного toolchain’а необходим Case-Sensitive Volume. Для этого воспользуемся замечательным скриптом. Опционально надо поправить в нем ${HOME} на то, куда вы реально хотите сохранять образ - у меня например второй большой диск (HDD против SSD) для всякого такого, поэтому пришлось поправить это дело.
Заодно сделаем его, воспользовавшись скриптом
./casesafe.sh create
./casesafe.sh mount
После того как станет ненужным, не забудте сделать
Установка требуемых зависимостей через HomeBrew
brew install autoconf
brew install binutils
brew install gawk
brew install gmp
brew install gnu-sed
brew install help2man
brew install mpfr
brew install openssl
brew install pcre
brew install readline
brew install wget
brew install xz
Иногда brew может сказать что зависимость уже стоит, но устарела, тогда опционально можно сделать brew update …
Еще опционально просят поставить dupes/grep ввиду того, что libc результирующего toolchain’а будет неправильно сконфигурирована, ввиду отличия макового BSD grep от GNU grep. –with-default-names необходимо чтобы системный grep заменился новым, так как без этого параметры brew’шный grep поставиться как ggrep.
brew install grep --with-default-names
Я решил пойти по Hacker’s way и сделать все через исходники, а не через release tarballs. Поэтому первое что делаем - клонируем репу, нужен гит:
git clone https://github.com/crosstool-ng/crosstool-ng
Далее нам требуется выбрать конкретный релиз, релизы обозначены тегами, поэтому выгребем теги и зачекаутим нужный:
git fetch --all --tags --prune
git checkout tags/crosstool-ng-1.23.0 -b r1.23.0
Далее начинаем работу по сборке. Так как мы решили пойти Hacker’s Way, то будем запускать это дело из исходников (поэтому ./configure –enable-local).
./bootstrap
./configure --enable-local
make
Для проверки успешности сборки в текущей директории надо сделать
Должна вывестись инструкция.
Конфигурируем
Говорят, тем кто собирал линуксовые ядра все должно быть понятно, я просмотрел все опции и потыкал то, что мне надо.
А вообще можно сделать так
И выбрать что-то из уже готовых настроек. И затем посмотреть конфигурацию так
./ct-ng show-x86_64-ubuntu16.04-linux-gnu
Выбираем preset
./ct-ng x86_64-ubuntu16.04-linux-gnu
И можно оттюнить с помощью menuconfig
Проблемы
Может случится так, что вы получите такую ошибку
/Volumes/OSXElCapitan/Users/mrdekk/casesafe/.build/src/gdb-7.12.1/gdb/doublest.c:258:19: error: use of undeclared identifier 'min'; did you mean 'fmin'?
[ERROR] /Volumes/OSXElCapitan/Users/mrdekk/casesafe/.build/src/gdb-7.12.1/gdb/doublest.c:568:19: error: use of undeclared identifier 'min'; did you mean 'fmin'?
[ERROR] /Volumes/OSXElCapitan/Users/mrdekk/casesafe/.build/src/gdb-7.12.1/gdb/doublest.c:912:25: error: use of undeclared identifier 'min'; did you mean 'fmin'?
[ERROR] make[3]: *** [doublest.o] Error 1
[ERROR] make[3]: *** Waiting for unfinished jobs....
[ERROR] make[2]: *** [all-gdb] Error 2
[ERROR] make[1]: *** [all] Error 2
Надо попробовать ветку master в crosstool-ng. Однако bash на OSX слишком старый для bootstrap, поэтому придется поставить bash из brew и поправить shell в bootstrap.
После однако начинаются проблемы с sha512sum, такие
/Volumes/OSXElCapitan/Users/mrdekk/Documents/Utils/crosstool-ng/scripts/functions: line 786: sha512sum: command not found
Для этого делаем так
brew install coreutils
ln -s /usr/local/bin/gsha512sum /usr/local/bin/sha512sum
Еще там не собирается glibc с binutils-2.29.1, есть патч, который позволяет так собираться
char *loc1 __attribute__ ((nocommon));
char *loc2 __attribute__ ((nocommon));
compat_symbol (libc, loc1, loc1, GLIBC_2_0);
compat_symbol (libc, loc2, loc2, GLIBC_2_0);
/* Although we do not support the use we define this variable as well. */
char *locs __attribute__ ((nocommon));
compat_symbol (libc, locs, locs, GLIBC_2_0);
Еще появилась такая проблема
[ERROR] ../sysdeps/ieee754/dbl-64/e_pow.c:469:13: error: '<<' in boolean context, did you mean '<' ? [-Werror=int-in-bool-context]
[ERROR] ../sysdeps/ieee754/dbl-64/e_pow.c:471:17: error: '<<' in boolean context, did you mean '<' ? [-Werror=int-in-bool-context]
[ERROR] ../sysdeps/ieee754/dbl-64/e_pow.c:477:9: error: '<<' in boolean context, did you mean '<' ? [-Werror=int-in-bool-context]
[ERROR] ../sysdeps/ieee754/dbl-64/e_pow.c:479:13: error: '<<' in boolean context, did you mean '<' ? [-Werror=int-in-bool-context]
Для ее решения есть такой патч
diff --git a/sysdeps/ieee754/dbl-64/e_pow.c b/sysdeps/ieee754/dbl-64/e_pow.c
index 663fa39..bd758b5 100644
--- a/sysdeps/ieee754/dbl-64/e_pow.c
+++ b/sysdeps/ieee754/dbl-64/e_pow.c
@@ -466,15 +466,15 @@ checkint (double x)
return (n & 1) ? -1 : 1; /* odd or even */
if (k > 20)
{
- if (n << (k - 20))
+ if (n << (k - 20) != 0)
return 0; /* if not integer */
- return (n << (k - 21)) ? -1 : 1;
+ return (n << (k - 21) != 0) ? -1 : 1;
}
if (n)
return 0; /*if not integer */
if (k == 20)
return (m & 1) ? -1 : 1;
- if (m << (k + 12))
+ if (m << (k + 12) != 0)
return 0;
- return (m << (k + 11)) ? -1 : 1;
+ return (m << (k + 11) != 0) ? -1 : 1;
}
Далее опять проблемы
[ALL ] rpc_parse.c: In function 'get_prog_declaration':
[ERROR] rpc_parse.c:543:23: error: '%d' directive writing between 1 and 10 bytes into a region of size 7 [-Werror=format-overflow=]
[ALL ] sprintf (name, "%s%d", ARGNAME, num); /* default name of argument */
[ALL ] ^~
[ALL ] rpc_parse.c:543:20: note: directive argument in the range [1, 2147483647]
[ALL ] sprintf (name, "%s%d", ARGNAME, num); /* default name of argument */
[ALL ] ^~~~~~
[ALL ] rpc_parse.c:543:5: note: 'sprintf' output between 5 and 14 bytes into a destination of size 10
[ALL ] sprintf (name, "%s%d", ARGNAME, num); /* default name of argument */
[ALL ] ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[ALL ] cc1: all warnings being treated as errors
[ERROR] make[3]: *** [/Volumes/OSXElCapitan/Users/mrdekk/casesafe/.build/x86_64-ubuntu16.04-linux-gnu/build/build-libc-final/multilib/sunrpc/rpc_parse.o] Error 1
[ERROR] make[3]: *** Waiting for unfinished jobs....
[ERROR] make[2]: *** [sunrpc/others] Error 2
[ERROR] make[1]: *** [all] Error 2
Решение этой проблемы найти было сложнее, но кажется есть вот
+--- a/sunrpc/rpc_parse.c
++++ b/sunrpc/rpc_parse.c
+@@ -521,7 +521,7 @@ static void
+ get_prog_declaration (declaration * dec, defkind dkind, int num /* arg number */ )
+ {
+ token tok;
+- char name[10]; /* argument name */
++ char name[MAXLINESIZE]; /* argument name */
+
+ if (dkind == DEF_PROGRAM)
+ {
Дальше опять проблемы
nss_nisplus/nisplus-alias.c:300:12: error: argument 1 null where non-null expected [-Werror=nonnull]
[ERROR] nss_nisplus/nisplus-alias.c:303:39: error: '%s' directive argument is null [-Werror=format-truncation=]
[ERROR] make[3]: *** [/Volumes/OSXElCapitan/Users/mrdekk/casesafe/.build/x86_64-ubuntu16.04-linux-gnu/build/build-libc-final/multilib/nis/nisplus-alias.os] Error 1
[ERROR] make[3]: *** Waiting for unfinished jobs....
[ERROR] make[2]: *** [nis/others] Error 2
[ERROR] make[1]: *** [all] Error 2
Лечаться так
diff --git a/nis/nss_nisplus/nisplus-alias.c b/nis/nss_nisplus/nisplus-alias.c
index 7f698b4e6d..509ace1f83 100644
--- a/nis/nss_nisplus/nisplus-alias.c
+++ b/nis/nss_nisplus/nisplus-alias.c
@@ -297,10 +297,10 @@ _nss_nisplus_getaliasbyname_r (const char *name, struct aliasent *alias,
return NSS_STATUS_UNAVAIL;
}
- char buf[strlen (name) + 9 + tablename_len];
+ char buf[tablename_len + 9];
int olderr = errno;
- snprintf (buf, sizeof (buf), "[name=%s],%s", name, tablename_val);
+ snprintf (buf, sizeof (buf), "[name=],%s", tablename_val);
nis_result *result = nis_list (buf, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL);
После этого toolchain собрался. После этого я попробовал собрать простой Hello, World с ним и запустить. На macOS не запустилось (zsh: exec format error: ./CrossWorld) - это хорошо. Собираем простейший контейнер на ubuntu 16.04 и запускаем - работает. Отлично!
04.12.2017
Крутится тут у меня небольшой контейнер с LAMP стэком (Apache, PHP, MySQL внутри одного контейнера вместе с supervisor). И в последнее время периодически стал вылетать MySQL (стал наедаться памяти). Помогала только ручная перезагрузка контейнера. Добавить памяти был не вариант (сама машинка ограниченная по ресурсам). Пробовал зашедулить в крон перезагрузку контейнера, но работало это плохо. Поэтому решил сделать что-нибудь чтоб жило само и без перезагрузок. Получилось так:
Проблема, как я уже сказал, ввиду ограниченных ресурсов виртуальной машинки - mysql хочет выделить блок памяти, но не может и поэтому падает совсем. Так как на диске место есть поколдуем так. Создадим специальный swap файлик:
dd if=/dev/zero of=/opt/swap.dat bs=1024 count=512k
mkswap /opt/swap.dat
swapon /opt/swap.dat
vim /etc/fstab
Файлик /etc/fstab отредактируем так, надо добавить в него такую строчку
/opt/swap.dat none swap sw 0 0
Далее подредактируем конфиг mysql
Уменьшим размер буфера
innodb_buffer_pool_size=64M
И далее пересоберем контейнер и перезапустим. 2 недели - полет нормальный. И да - эта инструкция как заставить работать на слабых конфигурациях. Может работать неоптимально с точки зрения производительности.
15.11.2017
Найдено тут, здесь в кратком изложении.
Задача - установить шрифт consolas (моноширный шрифт для разработки, очень приятный на вид) в Ubuntu, для целей разработки. Он по-умолчанию есть в Windows, но в Ubuntu по понятным причинам его нет.
Делаем так
sudo apt-get install font-manager
sudo apt-get install cabextract
Далее создаем скрипт, который скачает и распакует шрифты
Содержимое
#!/bin/sh
set -e
set -x
mkdir temp
cd temp
wget http://download.microsoft.com/download/E/6/7/E675FFFC-2A6D-4AB0-B3EB-27C9F8C8F696/PowerPointViewer.exe
cabextract -L -F ppviewer.cab PowerPointViewer.exe
cabextract ppviewer.cab
Выполняем
chmod +x consolas.sh
./consolas.sh
Устанавливаем шрифт Consolas
Шрифт Consolas содержится в таких файлах
- CONSOLAB.TTF - Жирный (bold)
- CONSOLAI.TTF - Курсив (italic)
- CONSOLA.TTF - Обычный (regular)
- CONSOLAZ.TTF - Жирный курсив (bold italic)