Преамбула
Есть у меня несколько старых проектов, писанных на С++, которые все еще развиваю по мере сил. Казалось бы — в чем же дело? Увы, это пачка очередных плагинов под мой любимый Adobe InDesign.
И каждый раз, когда выходит новый Creative Suite, приходится портировать это дело. Что интересно, основные усилия уходят на то, чтобы собрать новую версию по новым правилам, и подогнуть инсталлятор. Потому как уж если дошел до стадии «оно компилируется», то как правило — работает. Хотя конечно есть нюансы — например в один прекрасный момент PlaceGun перестал раскладывать несколько выбранных изображений, только первое. Но об этом — в следующий раз.
И разумеется — хотелось бы это собирать под все версии и все платформы за раз, а не «открыл вижлу — собрал — закрып — повторил».
Итак, для сборки, нам нужны одновременно
- MS VS 2005
- MS VS 2005 sp1
- MS VS 2008
- MS VS 2010
- MS VS 2012
Переходим на gmake — профит
Почему так? Ах, я не сказал — еще есть MacOSX. Так что прощай nmake без вариантов.
И в итоге получаем вот такой кошмар:
- cl-cs3.mak
- cl-cs4.mak
- cl-cs5.mak
- cl-cs55.mak
- cl-cs6.mak
- cl-cc.mak
- cl-cc2014.mak
- cl.mak
- commplugs.mak
- gcc-cs3.mak
- gcc-cs4.mak
- gcc-cs5.mak
- gcc-cs55.mak
- gcc-cs6.mak
- gcc-cc.mak
- gcc-cc2014.mak
- gcc.mak
- mac-defs.mak
- platform-impl.mak
- platform-targets.mak
- platform.mak
- win-defs.mak
А запускается это примерно так:
make ARCH=x64 INDD=cc2014 compile
Что очевидно, платформа определяется через uname. А каждая часть собирается стандартным образом как make -C foo.
Описание каждого компонента выглядит примерно так
include ../../make/platform.mak
TARGET=../../../lib/$(ARCH)/$(INDD)/libz.$(LibSuffix)
OBJS=
$(ARCH)/$(INDD)/adler32.$(OBJSuffix)
$(ARCH)/$(INDD)/compress.$(OBJSuffix)
$(ARCH)/$(INDD)/crc32.$(OBJSuffix)
$(ARCH)/$(INDD)/deflate.$(OBJSuffix)
$(ARCH)/$(INDD)/gzio.$(OBJSuffix)
$(ARCH)/$(INDD)/infback.$(OBJSuffix)
$(ARCH)/$(INDD)/inffast.$(OBJSuffix)
$(ARCH)/$(INDD)/inflate.$(OBJSuffix)
$(ARCH)/$(INDD)/inftrees.$(OBJSuffix)
$(ARCH)/$(INDD)/trees.$(OBJSuffix)
$(ARCH)/$(INDD)/uncompr.$(OBJSuffix)
$(ARCH)/$(INDD)/zutil.$(OBJSuffix)
all: $(TARGET)
$(TARGET): $(OBJS)
$(AR) $(LFLAGS) $(OBJS)
clean:
$(RMRF) $(TARGET) $(OBJS)
И все это работало на отдельно выделенной виртуалке под WinXP (и такой же под хакинтошем).
Расписывать эти страсти от и до смысла как бы не вижу, приведу только наиболее интересные выдержки. Например, вот так вычисляем project root и платформу, на которой сейчас будет выполняться компиляция:
PR:=$(subst make/,,$(dir $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))))
OSA:=$(shell uname -o)
ifeq (Darwin,$(OSA))
OS=mac
else
OS=win
endif
А вот так определяется где искать Boost (адобы в каждой версии умудряются переложить, поэтому тут две части — определяем макрос и потом его используем):
ifeq ($(ARCH),x86)
BoostARCH=32
else
BoostARCH=64
endif
BoostLib=$(subst ,/,$(AdobeSDK))/external/dva/third_party/boost_libraries/bin.v2/libs/boost_$1/lib/$(OS)/release/$(BoostARCH)/boost_$1.$(LibSuffix)
BoostFilesystemLib=$(call BoostLib,filesystem)
BoostThreadLib=$(call BoostLib,threads)
BoostRegexLib=$(call BoostLib,regex)
BoostSystemLib=$(call BoostLib,system)
Понятно, что набирал я это не руками, а написал пачку батников, да под MinGW:
@echo off
rem genmake component-name [path-to-sources [target-directory]]
set CN=%1
set FP=%2
if -%1==- set CN=default
if -%2==- set FP=.
if -%3==- set TD=.
echo> MakeF COMPONENT=%CN%
echo>> MakeF include ../make/platform.mak
echo>> MakeF TARGET=$(OBJDIR)/$(COMPONENT).$(DLLSuffix)
echo>> MakeF OBJS=
find %FP% -type f -name '*.c*' | grep -v .svn | awk '{ print "t$(OBJDIR)/" $1 ".$(OBJSuffix)t\"; }' | sed -e 's/.cpp././' | sed -e 's/.cxx././' | sed -e 's?/%FP%??' >> MakeF
echo>> MakeF #
echo>> MakeF HEADERS=
find %FP% -type f -name '*.h*' | grep -v .svn | awk '{ print "t" $1 "t\"; }' > headers >> MakeF
echo>> MakeF #
echo>> MakeF #
echo>> MakeF all: $(TARGET)
echo>> MakeF #
echo>> MakeF $(TARGET): $(OBJS)
echo>> MakeF $(LINK) $(LDFLAGS) $(OBJS) $(XLIBS)
echo>> MakeF if [ -f $@.manifest ] ; then mt -nologo -manifest $@.manifest "-outputresource:$@;2"; fi
echo>> MakeF #
echo>> MakeF clean:
echo>> MakeF rm -f $(TARGET) $(OBJS)
echo>> MakeF #
find %FP% -type f -name '*.c*' | grep -v .svn | awk '{ print "$(OBJDIR)/" $1 ".$(OBJSuffix) : " $1 " $(HEADERS)nt$(CC) $(CFLAGS) " $1 "n"; }' | sed -e 's/.cpp././' | sed -e 's/.cxx././' | sed -e 's?/%FP%/?/?' >> MakeF
echo>> MakeF #EOF
mv MakeF Makefile
@echo off
rem genmake component-name [path-to-sources [target-directory]]
set CN=%1
set FP=%2
if -%1==- set CN=default
if -%2==- set FP=.
if -%3==- set TD=.
echo> MakeF COMPONENT=%CN%
echo>> MakeF include ../make/platform.mak
echo>> MakeF TARGET=$(OBJDIR)/lib$(COMPONENT).$(LibSuffix)
echo>> MakeF OBJS=
find %FP% -type f -name '*.c*' | grep -v .svn | awk '{ print "t$(OBJDIR)/" $1 ".$(OBJSuffix)t\"; }' | sed -e 's/.cpp././' | sed -e 's/.cxx././' | sed -e 's?/%FP%??' >> MakeF
echo>> MakeF #
echo>> MakeF HEADERS=
find %FP% -type f -name '*.h*' | grep -v .svn | awk '{ print "t" $1 "t\"; }' > headers >> MakeF
echo>> MakeF #
echo>> MakeF #
echo>> MakeF $(TARGET): $(OBJS)
echo>> MakeF $(AR) $(LFLAGS) $(OBJS)
echo>> MakeF #
echo>> MakeF clean:
echo>> MakeF rm -f $(TARGET) $(OBJS)
echo>> MakeF #
find %FP% -type f -name '*.c*' | grep -v .svn | awk '{ print "$(OBJDIR)/" $1 ".$(OBJSuffix) : " $1 " $(HEADERS)nt$(CC) $(CFLAGS) " $1 "n"; }' | sed -e 's/.cpp././' | sed -e 's/.cxx././' | sed -e 's?/%FP%/?/?' >> MakeF
echo>> MakeF #EOF
mv MakeF Makefile
@echo off
setlocal
rem genmake plugin-name [path-to-sources [target-directory]]
set CN=%1
set FP=%2
if -%1==- set CN=plugin
if -%2==- set FP=.
if -%3==- set TD=.
echo> MakeF COMPONENT=%CN%
echo>> MakeF include ../make/platform.mak
echo>> MakeF PluginName=$(COMPONENT)
echo>> MakeF TARGET_DIR=$(OBJDIR)/$(PluginPrefix)
echo>> MakeF TARGET=$(TARGET_DIR)/$(PluginName)$(PluginSuffix)
echo>> MakeF CFLAGS+=-I ../common
echo>> MakeF LIBS+=$(call add_component_ref,vl) $(call add_component_ref,common)
echo>> MakeF OBJS=
find %FP% -type f -name '*.c*' | grep -v .svn | awk '{ print "t$(OBJDIR)/" $1 ".$(OBJSuffix)t\"; }' | sed -e 's/.cpp././' | sed -e 's/.cxx././' | sed -e 's?/%FP%??' >> MakeF
echo>> MakeF $(COMMON_PLUGIN_OBJS)
echo>> MakeF #
echo>> MakeF HEADERS=
find %FP% -type f -name '*.h*' | grep -v .svn | awk '{ print "t" $1 "t\"; }' > headers >> MakeF
echo>> MakeF #
echo>> MakeF #
echo>> MakeF ifeq (win,$(OS))
echo>> MakeF OBJS+= $(OBJDIR)/$(COMPONENT).res
echo>> MakeF FRES_TGT=$(OBJDIR)/$(COMPONENT)_w.$(FRES)
echo>> MakeF else
echo>> MakeF FRES_TGT=$(OBJDIR)/R/$(COMPONENT)_w.$(FRES)
echo>> MakeF endif
echo>> MakeF #
echo>> MakeF ifeq (mac,$(OS))
echo>> MakeF ifeq (x64,$(ARCH))
echo>> MakeF TARGET:=
echo>> MakeF endif
echo>> MakeF endif
echo>> MakeF #
echo>> MakeF all: $(TARGET)
echo>> MakeF #
echo>> MakeF clean:
echo>> MakeF rm -rf $(TARGET) $(OBJS) $(TARGET_DIR)/*
echo>> MakeF #
echo>> MakeF #
echo>> MakeF $(TARGET): $(OBJS) $(LIBS) $(FRES_TGT)
echo>> MakeF $(LINK) $(LDFLAGS) $(LDFLAGS_InDesignPlugin) $(OBJS) $(LIBS) $(AdobeLIBS) $(XLIBS)
echo>> MakeF #
echo>> MakeF #
echo>> MakeF ifeq ($(OS),win)
echo>> MakeF $(OBJDIR)/$(COMPONENT)_w.$(FRES): $(COMPONENT).fr
echo>> MakeF $(ODFRC) $(ODFRCFLAGS) -o $(call unix_to_dos,$(OBJDIR)/$(COMPONENT)_w.$(FRES)) $(COMPONENT).fr
echo>> MakeF #
echo>> MakeF $(OBJDIR)/$(COMPONENT).res: $(COMPONENT).rc $(OBJDIR)/$(COMPONENT)_w.$(FRES)
echo>> MakeF $(RSC) $(RSCFLAGS) $(CFLAGS_INCLUDE) $(COMPONENT).rc
echo>> MakeF $(AdobeSDK)devtoolsbinmerge_res.cmd $(call unix_to_dos,$(OBJDIR)) $(COMPONENT) $(COMPONENT)_w
echo>> MakeF mkdir -p "$(TARGET_DIR)/($(PluginName) Resources)"
echo>> MakeF cp -r $(OBJDIR)/idrc_* "$(TARGET_DIR)/($(PluginName) Resources)"
echo>> MakeF endif
echo>> MakeF ifeq ($(OS),mac)
echo>> MakeF $(OBJDIR)/R/$(COMPONENT)_w.$(FRES): $(COMPONENT).fr
echo>> MakeF mkdir -p $(TARGET_DIR)/Resources
echo>> MakeF mkdir -p $(OBJDIR)/R
echo>> MakeF $(ODFRC) $(ODFRCFLAGS) $(CFLAGS_IXA_OF) -o $@ $(COMPONENT).fr
echo>> MakeF /Developer/Tools/ResMerger -dstIs DF $@ -o $(OBJDIR)/$(PluginName).$(FRES)
echo>> MakeF /Developer/Tools/ResMerger $(OBJDIR)/$(PluginName).$(FRES) -dstIs DF -o $(TARGET_DIR)/Resources/$(PluginName).rsrc
echo>> MakeF cp -r $(OBJDIR)/R/idrc_* Info.plist $(TARGET_DIR)/Resources
echo>> MakeF ( cd $(TARGET_DIR)/..; ln -s A Current ; cd .. ; ln -s Versions/Current/Resources Resources ; ln -s Versions/Current/$(PluginName) $(PluginName) )
echo>> MakeF endif
echo>> MakeF include ../make/commplugs.mak
echo>> MakeF #
echo>> MakeF #
find %FP% -type f -name '*.c*' | grep -v .svn | awk '{ print "$(OBJDIR)/" $1 ".$(OBJSuffix) : " $1 " $(HEADERS)nt$(CC) $(CFLAGS) " $1 "n"; }' | sed -e 's/.cpp././' | sed -e 's/.cxx././' | sed -e 's?/%FP%/?/?' >> MakeF
echo>> MakeF #EOF
rem mv MakeF Makefile
endlocal
Ничто не предвещало, и ага
И все это прекрасно работало до того момента, пока не вышел Adobe InDesign CC 2014. И захотел вижуалстудию 2012. Вот тут-то белый пушной зверь каак выпрыгнул!
Нет, я конечно в теории знал, что вижуалстудия давным-давно не работает на ХРюше. Но вот то что cl.exe внезапно оказался not valid Win32 image — это был удар.
Немного поясню — еще со времен двух вижуалстудий 2005 с сервиспаком и без сервиспака одновременно, на билд машинку я ничего честно не ставлю. Для этого есть чистая виртуалка, в которую ставлю вижуалстудию express edition, накатываю правильный platform sdk, и то что получилось (лицензионно чистое и так далее) копирую в соответствующую папочку. А виртиуалку откатываю обратно до состояния «ничего не было».
И раз инсталлятор 2012 студии захотел поновее — не вопрос, вот вам Windows 8.1. Любой каприз — для микрософт ;-)
Ставлю, копирую — опаньки.
Месье знает толк в извращениях
И тут встал суровой силы вопрос — что делать?
Вариантов немного.
- Поставить и обжить новую виртуалку под Windows 8.1, начиная от MinGW и заканчивая индезигнами. И лицензии найти надо — они конечно у меня все есть, но лежат в совершенно разных местах. Долго и нудно.
- Перебраться в амазоново облако — на w2k12, хватит надолго и работать будет быстро. Но снова та же проблема — долго и нудно. И все эти накопившиеся версии и копии — 25 гигов перебрасывать. Лениво.
- Извернуться так чтобы не пришлось ничего менять.
Почесал я маковку, и подумал — а пуркуа бы не па? Ведь хостом у меня опенсусь стоит.
Набираю
wine где-там-скопировал-2012-студиюvcbincl.exe /help
и оно таки работает.
— Ага! — сказали суровые сибирские мужики.
Не без граблей конечно прошло — выяснилось что некоторые библиотеки от вижуалстудийного рантайма в вайне не совсем такие. Это не проблема — у вижуалстудии есть эталонные, скопировал по папочкам вижуалстудии.
А вот что удивило, совершенно простой mt.exe не только сам падал, но и вызывал SIGSEGV у wine. Шаманство с библиотеками решения не дало, пришлось по-быстрому написать свой заменитель с поэтессами.
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
static void usage() {
printf("Usage: mt.exe -manifest foo.dll.manifest -outputresource:foo.dll[;2]n");
}
static void alert(char* fn, char* msg, int code) {
static char* lpstrError;
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL, GetLastError(), LANG_USER_DEFAULT, &lpstrError, 1, NULL);
fprintf(stderr, "%s: %s: %s, %dn", fn, msg, lpstrError, code);
LocalFree(lpstrError);
}
static int update_res(char* ou, int resk, char* mf) {
HANDLE hUpdateRes;
LPVOID buf;
BOOL result;
FILE* fp;
struct _stat st;
int ressz = 0, outk;
fp = fopen(mf, "rb");
if(!fp) {
alert(mf, "could not open manifest file", errno);
return 2;
}
if(_fstat( fileno(fp), &st) != 0) {
fclose(fp);
alert(mf, "could not determine manifest file size", errno);
return 2;
}
ressz = st.st_size;
buf = (void*)malloc(ressz);
if(!buf) {
fclose(fp);
free(buf);
alert(mf, "could not allocate buffer for resource", ressz);
return 2;
}
outk = fread(buf, 1, ressz, fp);
if(outk != ressz) {
fclose(fp);
free(buf);
alert(mf, "could not read manifest", ressz - outk);
return 2;
}
fclose(fp);
hUpdateRes = BeginUpdateResourceA(ou, FALSE);
if (hUpdateRes == NULL) {
free(buf);
alert(ou, "Could not open file for writing.", 0);
return 3;
}
result = UpdateResourceA(hUpdateRes,
RT_MANIFEST,
MAKEINTRESOURCE(resk),
MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
buf,
ressz);
if (result == FALSE) {
alert(ou, "could not add resource", 0);
free(buf);
return 4;
}
if (!EndUpdateResource(hUpdateRes, FALSE)) {
alert(ou, "could not write changes to file", 0);
free(buf);
return 4;
}
free(buf);
return 0;
}
int main(int argc, char** argv)
{
char* mf = NULL;
char* ou = NULL;
char* v;
int resk = 1;
int k;
if(argc < 2) {
usage();
return 2;
}
for(k=1; k < argc; ++k) {
if(argv[k][0] == '-') {
if(argv[k][1] =='m') { // manifest
mf = argv[k+1];
++k;
continue;
}
else if(argv[k][1] == 'o' ) { // outputresource
if(argv[k+1])
ou = argv[k+1];
else {
ou = strchr(argv[k], ':');
if(!ou) {
usage();
return 3;
}
++ou;
}
++k;
v = strchr(ou, ';');
if(v) {
resk = atoi(v + 1);
*v = '';
}
else
resk = 1;
continue;
}
}
usage();
return 2;
}
if(!mf || !ou) {
usage();
return 2;
}
return update_res(ou, resk, mf);
}
А вот InnoSetup против ожиданий — сюрпризов не принес, собирает со свистом.
Пока — так, а далее поеду в облако, момент назрел.
Но все же решил поделиться с сообществом — вдруг я чего упускаю, вдруг кому пригодится…
Автор: viklequick