Как стать автором
Обновить

Почему важно собирать код из скриптов

Уровень сложностиПростой
Время на прочтение11 мин
Количество просмотров11K

В период с 199x по 202x на территории РФ развелось порядка двадцати тысяч программистов-микроконтроллеров, которые никогда в своей жизни не вылазили из всяческих GUI IDE (IAR, KEIL, Code Composer Studio, Atolic True Studio, CodeVision AVR, Segger Embedded Studio и прочие). Как по мне, дак это очень печально. Во многом потому, что специалист в Keil не сможет сразу понять как работать в IAR и наоборот. Другие файлы для настроек компоновщика. Другая xml настройки проекта. Миграция на другую IDE тоже вызывает большую трудность, так как это сводится к мышковозне в IDE-GUI. Каждая версия IAR не совместима с более новой версией IDE.

Дело в том, что GUI IDE появились в 199x...201x, когда не было расцвета DevOps(а), программист работал один и все действия выполнялись вручную. Мышкой. В то время работа в GUI казалась программистам-микроконтроллеров веселее, ведь в IDE много стразиков.

Но с усложнением кодовой базы, с увеличением сборок, с увеличением команд разработчиков появилась нужда в переиспользовании кода, нужда в автосборках, в авто тестах. Появилась методология

код отдельно, конфиги отдельно

и работа с IDE стала только тормозить процессы. Ведь конфиги хранятся в IDE-шной XML(ке). Приходилось дублировать конфиги платы для каждой сборки, что использовала эту плату. Пришлось дублировать код конфигов и этот процесс сопровождался ошибками, из-за человеческого фактора. При масштабировании работы с IDE кодовая база фактически превращалась в зоопарк в болоте.

Подробнее про этот колхоз можно почитать тут: Почему сборка из-под IDE это тупиковый путь

Какие недостатки сборки исходников из-под IDE?

  1. IDE монолитные и неделимые. Если Вы захотите поменять какую-то часть ToolChain(а): препроцессор, компилятор или компоновщик, а остальные фазы ToolChain(а) оставить как есть, то ничего из этого у вас не выйдет, так как капот IDE наглухо закрыт на замок.

  2. IDE стоят дорого, порядка 3500 EUR на один компьютер. Это лишняя дань и оброк для вашей компании. Оно вам надо?

    Cтоимость IAR 3690 EUR
    Cтоимость IAR 3690 EUR
  3. IDE(шные) xml очень слабо документированы или не документированы вовсе. У всех вендоров xml имеет свой особенный снобский xml-like язык разметки для конфигурации проекта. При внесении незначительных изменений появляется огромный git diff.

    Добавил в дерево IDE ссылку на одну папочку. Получился такой diff. Как думаете сколько часов его будут review(вить) ? Ответ: в среднем две недели
    Добавил в дерево IDE ссылку на одну папочку. Получился такой diff. Как думаете сколько часов его будут review(вить) ? Ответ: в среднем две недели
  4. Затруднена сборка из консоли. В основном инициировать сборку в IDE можно мышкой или горячими клавишами. Либо надо делать refresh мышкой из-под IDE.

  5. Обратная несовместимость с новыми версиями IDE.

  6. В условиях технологического Эмбарго и Санкций законно купить IDE европейского (Германия, Швеция, США) вендора невозможно. Они вас просто пошлют, так как у них Ваша территория числится как criminal state

  7. IDE отжирают какие-никакие но ресурсы компьютера, как RAM как и CPU, IDE же надо много оперативки, чтобы отрисовывать окошки со стразиками. IAR и Code Composer, например, раз в день стабильно напрочь зависают, что помогает лишь перезагрузка розеткой.

В общем распространение IDE это яркий пример известного ныне "технологического диктата" (vendor locking) запада для низкосортных народов из стран второго и третьего мира.

Навязывание GUI-IDE (Keil, IAR, CCS, плагинов Eclipse) это форма технологического диктата со стороны стран бывших метрополий. Они привыкли столетиями помыкать своими колониями и до сих пор продвигают эту подлую циничную политику подсовывая vendor loсking средств разработки во всякие страны как Кракожия, Такистан, Элбония, Западно-Африканская республика и прочее.

Сами они там у себя этим суррогатом нелепы не пользуются. Понимаете? У них там CMake, Make, Ninja скрипты сборки и полный DevOps в Docker контейнерах. Я работал в одной английской конторе и видел это всё своими глазами.

А туземцам в СНГ они суют эту тухлую песочницу в которой только кривые куличи лепить возможно.

Мы вам продаём песочницу (IDE), а вы сидите там за бортиками, улыбаетесь и лепите свои, никому даром не нужные, куличики (прошивки-паршивки).

иллюстрация программирования микроконтроллеров в IDE
иллюстрация программирования микроконтроллеров в IDE

Понятное дело, что разработчики IDE во всю пользуются ситуацией и добавляют всяческие программные закладки, такие как слив исходников в здание с пятью углами, удаленное отключение функционала, ограничение размера выходного бинарного файла до 32kByte и всё на, что им там только хватит их извращённой фантазии!

Понятное дело, что в таких жестких рамках на "сделать что-то серьезное" туземцам рассчитывать не приходится. Сборка в IDE это всегда мелкая серия. Всегда малый ассортимент. Всегда ручное развертывание.

А когда запад нас в очередной раз кинул пришлось начать думать как теперь дальше жить... Хорошим решением оказалось сделать шаг назад в 197x 198x когда на компьютерах всё делали из консоли. Даешь сборку сорцов из скриптов! Можно вообще *.bat файл написать и он, в общем-то, инициирует запуск нужных утилит, однако исторически Си-код собирали культовой утилитой make.

Дело в том, что makefile придумали в 1976 (Stuart Feldman), тогда, когда к компьютеры были дорогие (65k USD только за несколько сотен килобайт RAM) и к компьютерам подпускали только PhD профессоров из топовых университетов мира. Поэтому и появились такие гениальнейшие утилиты как awk, make, grep, find, gdb, sed, sort, tsort, uniq, tr и прочие.

Тогда в далеких 197x у школоты в принципе не было возможности хоть как-то влиять на ход развития софта и генерировать спагетти код и программные смеси как сейчас в 200x...202x.

Сейчас же в 201x...202x какой-нибудь 43-летний Junior-embedded программист-микроконтроллеров, привыкший к GUI-IDE, может логично провозгласить:

Я вообще не представляю, как без помощи IDE и зелёного треугольника вверху производить пошаговую отладку программы?

Тут есть 4+ ответа:
1--> Использовать связку GDB Client + GDB Server и отлаживать код из командной строки.
https://habr.com/ru/post/694708/

2--> Отлаживать код через интерфейс командной строки CLI поверх UART.
https://habr.com/ru/post/694408/

3--> В коем-то веке покрывать свой код модульными тестами
https://habr.com/ru/post/698092/

4--> Использовать другие косвенные признаки отладки кода: HeartBeat LED, Утилита arm-none-eabi-addr2line.exe, Assert(ы), DAC, STM Studio (Аналог ArtMoney из GameDev(a)), Health Monitor
https://habr.com/ru/post/681280/

В чем достоинства сборки С-кода из Make файлов?

  1. Makefile это самый гибкий способ управлять модульностью кодовой базы. Можно буквально одной строчкой (например FREE_RTOS=Y) добавлять или исключать один конкретный программный компонент (десятки файлов) из десятков сборок. В случае же сборки из-под IDE вам бы пришлось вручную мышкой редактировать .xml для каждой отдельной сборки.

  2. Сборку из Makefile очень легко автоматизировать. Достаточно в консоли выполнить make all и у вас инициируется процесс сборки, а через 3 мин в соседней папке будут лежать артефакты.

  3. После сборки из скриптов вы получите полный лог сборки, в то время как IDE обычно показывают последние 3-4 экрана сообщений компилятора.

  4. В MakeFile очень просто менять компиляторы. Это, буквально, заменить одну строчку. С GCC на Clang или на GHS. Вот типичный основной makefile для любой сборки на ARM Cortex-Mxx

mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
$(info Build  $(mkfile_path) )
BUILD_DIR = build

#@echo $(error SOURCES_C= $(SOURCES_C))
INCDIR := $(subst /cygdrive/c/,C:/, $(INCDIR))
#@echo $(error INCDIR=$(INCDIR))
SOURCES_C := $(subst /cygdrive/c/,C:/, $(SOURCES_C))
#@echo $(error SOURCES_C=$(SOURCES_C))
SOURCES_ASM := $(subst /cygdrive/c/,C:/, $(SOURCES_ASM))
LIBS  := $(subst /cygdrive/c/,C:/, $(LIBS))
LDSCRIPT := $(subst /cygdrive/c/,C:/, $(LDSCRIPT))
#@echo $(error SOURCES_ASM=$(SOURCES_ASM))

# binaries
PREFIX = arm-none-eabi-
GCC_PATH="C:/Program Files (x86)/GNU Arm Embedded Toolchain/10 2021.10/bin"
$(info GCC_PATH=$(GCC_PATH))

# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
ifdef GCC_PATH
  CC = $(GCC_PATH)/$(PREFIX)gcc
  AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
  CP = $(GCC_PATH)/$(PREFIX)objcopy
  SZ = $(GCC_PATH)/$(PREFIX)size
else
  CC = $(PREFIX)gcc
  AS = $(PREFIX)gcc -x assembler-with-cpp
  CP = $(PREFIX)objcopy
  SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
 
# float-abi
ifeq ($(NRF5340), Y)
    ifeq ($(CORE_NET), Y)
        FLOAT-ABI = -mfloat-abi=soft
        OPT += -fsingle-precision-constant
    endif
    
    ifeq ($(CORE_APP), Y)
        FLOAT-ABI = -mfloat-abi=hard
    endif
else
   FLOAT-ABI = -mfloat-abi=hard
endif

# mcu
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)

# macros for gcc
#CSTANDARD = -std=c11
CSTANDARD = -std=gnu99
# AS defines
AS_DEFS = 

# AS includes
AS_INCLUDES = 

ifeq ($(DEBUG), Y)
    #@echo $(error DEBUG=$(DEBUG))
    CFLAGS += -g3 -gdwarf-2 -ggdb
    OPT += -O0 
else
    OPT += -Os
endif
OPT += -fmessage-length=0    
OPT += -fsigned-char
OPT += -fno-common
OPT += -fstack-usage
OPT += -finline-small-functions

#Perform dead code elimination
OPT += -fdce

#Perform dead store elimination
OPT += -fdse

# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

CFLAGS += $(CSTANDARD)
CFLAGS += -Wall
#CFLAGS += -Wformat-overflow=1
CFLAGS += $(MCU) $(OPT) -fdata-sections -ffunction-sections $(INCDIR)  

# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"

# LDFLAGS

# libraries
LINKER_FLAGS += -Xlinker --gc-sections 
ifeq ($(MBR), Y)
    #@echo $(error MBR=$(MBR))
    LIBS += -lnosys
    LDFLAGS += -specs=nano.specs
else
    LINKER_FLAGS += -u _scanf_float
    LINKER_FLAGS += -u _printf_float
endif
#LINKER_FLAGS += -lrdimon --specs=rdimon.specs

ifeq ($(LIBC), Y)
    #@echo $(error LIBC=$(LIBC))
    LIBS += -lc
endif

ifeq ($(MATH), Y)
    #@echo $(error MATH=$(MATH))
    LIBS += -lm 
endif


#@echo $(error LDSCRIPT=$(LDSCRIPT))
LIBDIR = 

LDFLAGS += $(MCU) -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections $(LINKER_FLAGS)

# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin


# build the application
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(SOURCES_C:.c=.o)))
vpath %.c $(sort $(dir $(SOURCES_C)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(SOURCES_ASM:.S=.o)))
vpath %.S $(sort $(dir $(SOURCES_ASM)))

$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) 
	$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@

$(BUILD_DIR)/%.o: %.S Makefile | $(BUILD_DIR)
	$(AS) -c $(CFLAGS) $< -o $@

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
	$(CC) $(OBJECTS) $(LDFLAGS) -o $@
	$(SZ) $@

$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(HEX) $< $@
	
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(BIN) $< $@	
	
$(BUILD_DIR):
	mkdir $@		

# clean up
clean:
	-rm -fR $(BUILD_DIR)
  
# dependencies
-include $(wildcard $(BUILD_DIR)/*.d)

# *** EOF ***

5--Когда вы собираете из Make вы можете не только собирать исходники, но и

Утилите make всё равно какие консольные утилиты вызывать. Понимаете? Это универсальный способ определения программных конвейеров.

Утилиту make можно использовать не только для дирижирования процессом сборки кода программ. Утилита make может также управлять авто генерацией преобразования расширений файлов для черчения или управлять сборкой документации, управлять DevOps(ом). Make можно использовать по-разному, как только фантазии хватит. Ибо Make совершенно инвариантен и абстрагируется от языка программирования как такового.

  1. Для каждой сборки надо самим писать крохотный Makefile

MK_PATH:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
#@echo $(error MK_PATH=$(MK_PATH))
WORKSPACE_LOC:=$(MK_PATH)../../

INCDIR += -I$(MK_PATH)
INCDIR += -I$(WORKSPACE_LOC)

#@echo $(error SOURCES_C=$(SOURCES_C))
include $(MK_PATH)config.mk
include $(MK_PATH)cli_config.mk
include $(MK_PATH)diag_config.mk
include $(MK_PATH)test_config.mk

include $(WORKSPACE_LOC)code_base.mk
include $(WORKSPACE_LOC)rules.mk
 

и конфиг для сборки.


mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
$(info Build  $(mkfile_path) )

TARGET=pastilda_r1_1_generic
#@echo $(error TARGET=$(TARGET))
AES256=Y
ALLOCATOR=Y

......

USB_HOST_HS=Y
USB_HOST_PROC=Y
UTILS=Y
XML=Y

Для каждого компонента *.mk файл. Язык make простой. Он, в сущности, очень похож на bash. Вот типичный *.mk файл для драйвера UWB радио-трансивера DW1000.

ifneq ($(DWM1000_MK_INC),Y)
    DWM1000_MK_INC=Y

    DWM1000_DIR = $(DRIVERS_DIR)/dwm1000
    mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
    $(info Build $(mkfile_path) )
    $(info + DWM1000)

    INCDIR += -I$(DWM1000_DIR)
    OPT += -DHAS_DWM1000
    OPT += -DHAS_DWM1000_PROC
    OPT += -DHAS_UWB

    DWM1000_RANGE_DIAG=Y
    DWM1000_RANGE_COMMANDS=Y

    DWM1000_OTP_COMMANDS=Y
    DWM1000_OTP_DIAG=Y

    SOURCES_C += $(DWM1000_DIR)/dwm1000_drv.c

    include $(DWM1000_DIR)/otp/dwm1000_otp.mk
    include $(DWM1000_DIR)/registers/dwm1000_registers.mk

    ifeq ($(DWM1000_RANGE),Y)
        include $(DWM1000_DIR)/range/dwm1000_range.mk
    endif

    ifeq ($(DIAG),Y)
        ifeq ($(DWM1000_DIAG),Y)
            $(info +DWM1000_DIAG)
            OPT += -DHAS_DWM1000_DIAG
            SOURCES_C += $(DWM1000_DIR)/dwm1000_diag.c
        endif
    endif

    ifeq ($(CLI),Y)
        ifeq ($(DWM1000_COMMANDS),Y)
            $(info +DWM1000_COMMANDS)
            OPT += -DHAS_DWM1000_COMMANDS
            SOURCES_C += $(DWM1000_DIR)/dwm1000_commands.c
        endif
    endif
endif
  1. Параллельное написание Make файлов и С-кода стимулирует придерживаться модульности, изоляции программных компонентов и к прослеживанию зависимостей между компонентами. Если вы пишите код и make файлы примерно параллельно, то очень вероятно, что у вас получится чистый, аккуратный репозиторий сам собой.

  2. Makefile(лы) хороши тем, что можно добавить много проверок зависимостей и assert(ов) на фазе отработки Make-скриптов прямо в *.mk файлах еще до компиляции самого кода, даже до запуска препроцессора, так как язык программирования make поддерживает условные операторы и функции. Можно очень много ошибок отловить на этапе отработки утилиты make.

  3. Язык make очень прост. Вся спека GNU Make это всего 226 страниц. Cтю Фельдман (автор make) просто гений.

  4. Makefile(лы) прозрачные потому что текстовые. Всегда видно, где опции препроцессора, где ключи для компилятора, а где настройки для компоновщика. Всё, что нужно можно найти утилитой grep в той же консоли от GIT-bash даже при работе в Windows 10.

  5. Конфиг для сборки можно формировать как раз на стадии make файлов и передавать их как ключи для препроцессора. Таким образом конфиги будут видны в каждом *.с файле проекта и не надо вставлять #include(ы) c конфигами. Всё можно передать как опции утилите cpp (препроцессора).

  6. При сборке из makefile вам вообще всё равно для какой целевой платформы собирать код прошивки. Вы можете минимальными изменениями в makefile собрать прошивку и крутить её даже на x86. Вместо UART имитировать CLI в Windows консольном приложении на PC .

    эмулятор прошивки в консольном приложении
    эмулятор прошивки в консольном приложении
  7. Внутри makefile вы можете выполнить какой-нибудь полезный скрипт. Например подписать прошивку состоянием репозитория

    GIT_SHA := $(shell git rev-parse --short HEAD)
    OPT += -DGIT_SHA=0x0$(GIT_SHA)

затем в коде написать

    LOG_INFO(SYS,"GitSha: 0x%08x", GIT_SHA);

таким образом 3мя строчками вы сделаете то, что отдельными скриптами заняло бы 50+ строк.

  1. При сборке из скриптов (например из Make) очень легко добавить новую сборку. Достаточно только написать конфиг и 3 строчки в отдельном Makefile и вот у вас новая специфическая сборка. Одновременно с этим создание новой сборки в GUI-IDE сопряжено с многочисленной мышкавозней и дублированием конфигов!

Вывод

В наше время бахвалиться навыками пользования всяческими IDE должно быть уже стыдно. Надо признать что программирование внутри IDE - это уровень кружка робототехники 8-9го класса средней общеобразовательной школы.

Сборка прошивок GUI-IDE плагинами, а также из-под Keil, IAR ССS - это признак Junior разработчика.

А сборка из скриптов - это фундаментальная технология, которая по плечу только программистам с качественным опытом.

Санкционные реалии таковы, что настало время, чтобы российских программистов-микроконтроллеров из детского садика под названием "GUI-IDE" перевести, наконец, в школу (т.е. приучить к makefile или хотя бы к CMake). А дальше приобщать к полноценному DevOps(у).

При этом надо смотреть в сторону Make, CMake. Как вариант Ninja. Make это как пуговицы. Старая, простая и очень полезная вещь.

Откровенно говоря, только тех, кто умеет собирать проекты из скриптов и можно считать настоящими программистами. Те кто "программисты" которые не умеет читать писать скрипты сборки, это либо самозванцы, либо обыкновенная шко-ло-та. Без обид, но что есть, то есть.

При сборке из makefile прошивки для микроконтроллеров всех vendor(ов) собираются одинаково. Будь-то stm32, nrf53, cc26x42, atmega8 или spc58nn. Надо просто открыть консоль и набрать make all. Easy!

Собираете свои прошивки из make в этом нет ничего сложного.

Links

Так как make появился ещё в 197x, то это пожалуй самая изученная тема во всем Computer Science. Материалов для обучения make просто немерено.

Пояснение

URL

1

Эффективное использование GNU Make

https://www.opennet.ru/docs/RUS/gnumake/

14

CI/CD прошивок для микроконтроллеров в Wiren Board ( начало на 25:20)

https://www.youtube.com/watch?v=HEEVxZ4rBCo

2

Конвеерум #30: Эволюция рабочего окружения для embedded разработки

https://www.youtube.com/watch?v=vmuO4bHjTSo&t=7s

3

GNU Make может больше чем ты думаешь

https://habr.com/ru/post/47513/

4

Пример Makefile

https://habr.com/ru/post/111691/

5

Настройка ToolChain(а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB

https://habr.com/ru/post/673522/

6

Компактный make для STM32 с USB

https://habr.com/ru/post/724800/

7

Генерация Зависимостей Внутри Программы

https://habr.com/ru/articles/765424/

8

Эффективное использование GNU Make

http://citforum.ru/operating_systems/gnumake/

9

GNU Make

https://www.gnu.org/software/make/

10

How To Create A Makefile (C/C++) | Makefile Tutorial | Linux

https://www.youtube.com/watch?v=WDXG8gpuVqg&list=WL&index=1

11

GNU Make

https://rus-linux.net/nlib.php?name=/MyLDP/algol/gnu_make/gnu_make_3-79_russian_manual.html

12

Техникум: Автоматическое Aрхивирование Aртефактов

https://habr.com/ru/articles/826730/

13

Директива vpath

https://runebook.dev/ru/docs/gnu_make/selective-search

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы собираете код с помощью утилиты Make?
46.77% да87
53.23% нет99
Проголосовали 186 пользователей. Воздержался 21 пользователь.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы собираете прошивки из-под IDE?
63.22% да110
36.78% нет64
Проголосовали 174 пользователя. Воздержались 22 пользователя.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы собираете код с помощью СMake?
45.16% да84
54.84% нет102
Проголосовали 186 пользователей. Воздержались 22 пользователя.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы собираете прошивки в IDE-IAR?
19.7% да13
80.3% нет53
Проголосовали 66 пользователей. Воздержались 8 пользователей.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы собираете прошивки в IDE-Keil?
31.34% да21
68.66% нет46
Проголосовали 67 пользователей. Воздержались 7 пользователей.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы собираете код прошивок с помощью самописных Ninja скриптов?
6.15% да4
93.85% нет61
Проголосовали 65 пользователей. Воздержались 8 пользователей.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы собираете программы из скриптов?
58.62% Да, я собираю программы из скриптов (Make/Ninja/Cmake и прочее). Сам пишу скрипты сборки.17
41.38% Нет. Я полностью доверяюсь GUI-IDE и просто нажимаю F11, а там и будь, что будет.12
Проголосовали 29 пользователей. Воздержались 5 пользователей.
Теги:
Хабы:
Всего голосов 44: ↑12 и ↓32-18
Комментарии73

Публикации

Истории

Работа

DevOps инженер
54 вакансии
Программист С
55 вакансий

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
10 – 11 октября
HR IT & Team Lead конференция «Битва за IT-таланты»
МоскваОнлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн